Меня зовут Райан, и я ненавижу жаргон. Я ненавижу жаргон, потому что я плохо подготовлен, чтобы его запомнить, но причина, по которой я все еще пишу достойный код, заключается в том, что я хорошо подготовлен для понимания концепций, на которые указывают эти жаргонные слова.

В этой статье я расскажу о трех взаимосвязанных вещах (тестирование, архитектура и удобочитаемость) и о том, когда вы должны начать о них заботиться. Хотя это отчасти субъективный вопрос, на самом деле существует довольно конкретный ответ на этот вопрос, и мне не нужно использовать громкие пугающие слова, чтобы объяснить его.

В этой статье рассматриваются:

  • Почему некоторые люди не беспокоятся о тестах, архитектуре и написании разборчивого кода, и когда это разумно
  • Как масштаб и сложность вашей программы могут дать вам четкое представление о том, когда вам следует позаботиться об этом
  • Какова реальная польза от применения этого метода, в ясных терминах и примерах.
  • Краткая серия вопросов, которые вы можете задать себе, чтобы облегчить принятие решения

Почему некоторые люди говорят «нет»?

Если вам не повезло, вы пытались изучить архитектуру программного обеспечения или протестировать свой код, то вы, вероятно, встречали:

  • Бесконечные библиотеки тестирования
  • Бесконечные мнения о том, какие библиотеки использовать
  • Бесконечные мнения о тестовом покрытии
  • Бесконечные споры о том, является ли разработка через тестирование обязательной или пустой тратой времени.
  • Бесконечные споры о лучшей программной архитектуре на все случаи жизни (совет: ее не существует)
  • И так далее…

Честно говоря, если единственное, что вас волнует, это время выполнения работ и сколько денег вы зарабатываете, то я абсолютно признаю, что архитектура программного обеспечения, тестирование и удобочитаемость может быть пустой тратой таких ресурсов. Не заблуждайтесь, существуют приложения дойной коровы, которые были созданы с использованием архитектуры большого шара грязи и случайно протестированы пользователями. Так что человек, который читает это, может разумно сказать, что если мне это не нужно, это просто пустая трата оплачиваемых часов и большого количества учебы.

Во-вторых, некоторые из вас, читающие это, являются новичками. Вы пишете очень маленькие приложения (что хорошо, продолжайте делать это!), В которых ваша цель - научиться нескольким вещам. Вам действительно нужно использовать тестовую, разработанную по контракту, многоуровневую, чистую архитектуру, чтобы построить простой калькулятор? Опять же, если бы я сказал, что вам нужно это сделать, то я, вероятно, полон чего-то, что начинается с «s» и заканчивается «хитом». Это зависит от того, зачем вы пишете программу.

Я действительно продаю вам эти вещи, не так ли?

Почему я все еще говорю "да"

Я собираюсь объяснить, что именно здесь происходит, очень простой аналогией, которая, вероятно, знакома любому, кто изучал элементарную алгебру. Вы помните, когда ваш учитель попросил вас решить что-то вроде этого…:

2х + 4 = 8; Решить относительно x

… И учитель попросил вас показать вашу работу? Надеюсь, что некоторые из вас только что поняли, к чему я стремлюсь, когда я утверждаю, что масштаб и сложность определяют эффективность (чистую выгоду) тестирования, архитектуру программного обеспечения и удобочитаемость.

Для тех, кто не видит, куда я иду, я немного подробнее остановлюсь на этом. Я был тем ребенком, который спрашивал учителя: «Зачем мне показывать свою работу, если я могу просто мгновенно обдумывать ответ в своей голове К сожалению, у меня не было хорошего учитель, и мне не дали ОЧЕНЬ ВНУТРЕННЮЮ ПРИЧИНУ, почему я должен практиковаться в прохождении этапов решения уравнения; даже когда мне это не нужно.

Когда-нибудь, ребята и девчонки, вы столкнетесь с уравнением, которое просто не сможете решить в своей голове. Да, даже «умные дети» в конечном итоге сталкиваются с проблемами, слишком сложными, чтобы их можно было решить мгновенно , вместо постепенного.

Теперь у нас есть:

  • Простой способ подумать об одной и той же концептуальной проблеме, выраженной как в математике, так и в вычислениях (не случайно)
  • Какой-то объективный потолок для экономии времени, делая что-то в своей голове, на самом деле будет стоить вам намного дороже, чем демонстрация своей работы и подтверждение каждого шага по ходу дела.

А как насчет моего кода?

Ниже приведен фактический фрагмент из моего проекта с открытым исходным кодом (который, по общему признанию, является скорее демонстрацией, чем чем-либо еще). Если вы не умеете читать Kotlin или просто не заинтересованы в его синтаксическом анализе, просто прокрутите вниз, где я резюмирую суть этого примера.

/**
 *
 * On start basically means that we want to render the UI. This depends on whether the user is
 * anonymous, or registered (logged out or in), and if they are in public or private mode
 * a. User is anonymous (always private in that case)
 * b. User is registered, private mode
 * c. User is registered, public mode
 *
 * a:
 *1. Check isPrivate status: true
 *2. Check login status in backend if necessary
 *3. parse datasources accordingly
 *4. draw view accordingly
 */
@Test
fun `On Start anonymous`() = runBlocking {
    every { vModel.getIsPrivateMode() } returns true
    every { vModel.getUserState() } returns null
    coEvery { anonymous.getNotes(noteLocator) } returns Result.build { getNoteList }

    logic.onChanged(NoteListEvent.OnStart)

    verify { vModel.getIsPrivateMode() }
    verify { vModel.getUserState() }
    verify { view.showList() }
    verify { adapter.submitList(getNoteList) }
    coVerify { anonymous.getNotes(noteLocator) }
}

/**
 * b:
 *1. Check isPrivate status: false
 *2. Check login status in backend if necessary
 *3. parse datasources accordingly
 *4. draw view accordingly
 *
 */
@Test
fun `On Start Registered Private`() = runBlocking {
    every { vModel.getIsPrivateMode() } returns true
    every { vModel.getUserState() } returns getUser()
    coEvery { registered.getNotes(noteLocator) } returns Result.build { getNoteList }

    logic.onChanged(NoteListEvent.OnStart)

    verify { vModel.getIsPrivateMode() }
    verify { vModel.getUserState() }
    verify { view.showList() }
    verify { adapter.submitList(getNoteList) }
    coVerify { registered.getNotes(noteLocator) }
}

/**
 * error:
 *1. Check isPrivate status: false
 *2. Check login status in backend if necessary
 *3. parse datasources accordingly
 *4. draw view accordingly
 *
 */
@Test
fun `On Start Error`() = runBlocking {
    every { vModel.getIsPrivateMode() } returns true
    every { vModel.getUserState() } returns getUser()
    coEvery { registered.getNotes(noteLocator) } returns Result.build { throw SpaceNotesError.RemoteIOException }

    logic.onChanged(NoteListEvent.OnStart)

    verify { vModel.getIsPrivateMode() }
    verify { vModel.getUserState() }
    verify { view.showEmptyState() }
    verify { view.showErrorState(MESSAGE_GENERIC_ERROR) }
    coVerify { registered.getNotes(noteLocator) }
}

/**
 * For empty list, leave the loading animation active.
 */
@Test
fun `On Start a with empty list`() = runBlocking {
    every { vModel.getIsPrivateMode() } returns true
    every { vModel.getUserState() } returns getUser()
    coEvery { registered.getNotes(noteLocator) } returns Result.build { emptyList<Note>() }

    logic.onChanged(NoteListEvent.OnStart)

    verify { vModel.getIsPrivateMode() }
    verify { vModel.getUserState() }
    verify { view.showEmptyState() }
    verify { adapter.submitList(emptyList<Note>()) }
    coVerify { registered.getNotes(noteLocator) }
}

Детали реализации здесь ни в прямом, ни в переносном смысле не интересны. Однако способ, которым я писал этот код, действительно весьма интересен (по крайней мере, для меня):

Первое, что я написал, - это простое описание происходящего:

При запуске в основном означает, что мы хотим визуализировать пользовательский интерфейс. Это зависит от того, является ли пользователь анонимным или зарегистрированным (вышел из системы или в системе), и находится ли он в общедоступном или частном режиме.

Вторая вещь, которую я написал, - это простой способ дифференцировать различные возможные потоки событий, которые могут возникать для этого отдельного объекта / пользовательской истории / чего угодно:

а. Пользователь анонимен (в этом случае всегда приватен)
b. Пользователь зарегистрирован, приватный режим
c. Пользователь зарегистрирован, публичный режим

Третье, что я написал для каждого потока событий, - это подробный список шагов псевдокода, необходимых для решения проблемы:

a:
1. Проверьте статус isPrivate: true
2. При необходимости проверьте статус входа в бэкэнд
3. Соответственно проанализируйте источники данных
4. Нарисуйте представление соответствующим образом

Я даже написал комментарий для крайнего случая, когда мы успешно извлекаем заметки пользователя, но этот список пуст:

Для пустого списка оставьте анимацию загрузки активной.

Четвертое, что я написал, - это сами тесты:

@Test
fun `On Start anonymous`() = runBlocking {
    every { vModel.getIsPrivateMode() } returns true
    every { vModel.getUserState() } returns null
    coEvery { anonymous.getNotes(noteLocator) } returns Result.build { getNoteList }

    logic.onChanged(NoteListEvent.OnStart)

    verify { vModel.getIsPrivateMode() }
    verify { vModel.getUserState() }
    verify { view.showList() }
    verify { adapter.submitList(getNoteList) }
    coVerify { anonymous.getNotes(noteLocator) }
}

Последнее, что я написал, - это реальный код, реализующий эти тестовые примеры, после того, как я запустил их, чтобы убедиться, что они правильно сформированы. Они должны завершиться ошибкой из-за того, что утверждения проверки не имеют истинного значения, чего следует ожидать от нереализованного модуля. Опять же, просто пропустите это, если чтение больших блоков кода на незнакомых языках вам не нравится (достаточно честно):

class NoteListLogic(dispatcher: DispatcherProvider,
                    val noteLocator: NoteServiceLocator,
                    val userLocator: UserServiceLocator,
                    val vModel: INoteListContract.ViewModel,
                    var adapter: NoteListAdapter,
                    val view: INoteListContract.View,
                    val anonymousNoteSource: AnonymousNoteSource,
                    val registeredNoteSource: RegisteredNoteSource,
                    val authSource: AuthSource)
    : BaseLogic(dispatcher), CoroutineScope, Observer<NoteListEvent<Int>> {
//...
override fun onChanged(event: NoteListEvent<Int>?) {
    when (event) {
        is NoteListEvent.OnNoteItemClick -> onNoteItemClick(event.position)
        //...
        is NoteListEvent.OnStart -> onStart()
        //...
    }
}

private fun onStart() {
    getListData(vModel.getIsPrivateMode())
}

fun getListData(isPrivateMode: Boolean) = launch {
    val dataResult = getPrivateListData()

    when (dataResult) {
        is Result.Value -> {
            vModel.setAdapterState(dataResult.value)
            renderView(dataResult.value)
        }
        is Result.Error -> {
            view.showEmptyState()
            view.showErrorState(MESSAGE_GENERIC_ERROR)
        }
    }
}

suspend fun getPrivateListData(): Result<Exception, List<Note>> {
    return if (vModel.getUserState() == null) {
        anonymousNoteSource.getNotes(noteLocator)
    }
    else {
        registeredNoteSource.getNotes(noteLocator)
    }
}

Теперь, когда у нас есть реальный код, в чем смысл? Выполнив описанный выше процесс, пятое, что я сделал на самом деле, это написал код, я сэкономил себе много времени.

Опять же, используя нашу предыдущую аналогию, ученик может поднять руку и сказать: Как, черт возьми, все это помогло, но не заняло дополнительное время?

Мы займемся этим сейчас.

- Дж. Кришнамурти

Фактические выгоды

Что, если бы я сказал вам, что, начиная с удобочитаемых абстрактных описаний некоторых аспектов моей программы, постепенно добавляя детали, включая подробные шаги и результаты событий, и создавая логически доказуемая проверка того, действительно ли мой код выполняет то, что он должен делать, я сделал сложную реализацию простой для написания?

Возможно, настолько просто, что мне потребовалось меньше времени в целом, чтобы проследить за этим процессом, чем если бы я просто слепо вскочил и раскрутил реализацию.

Возможно, за счет заблаговременного выявления большинства моих логических / канцелярских ошибок отдельного устройства в тестах JVM (локально или на сервере непрерывной интеграции), которые можно автоматизировать и работать быстрее чем создание и развертывание всего приложения на устройстве, я экономлю себе огромное время?

Возможно, поскольку у меня память о золотой рыбке и я не могу вспомнить, что я написал два часа назад, история пользователя, описание псевдокода и прохождение теста (надеюсь) означает, что я или следующий человек для поддержки этого проекта не нужно / не нужно даже помнить, что я делал несколько часов назад?

Вы улавливаете то, что я пишу здесь? Если нет, то один из нас потерпел неудачу.

Резюме

Вот нелинейная блок-схема (если такая существует?) Вопросов, которые нужно задать себе, прежде чем вы решите написать монолитный кусок мусора или чрезмерно сложный калькулятор :

  • Ваша основная цель - выпустить что-то, что сделано очень быстро и, надеюсь, функционирует достаточно хорошо, чтобы не получить плохие отзывы? Пропустите тестирование и архитектуру.
  • Вы пишете чертовски сложное приложение, и даже некоторые из более простых функций (с внешней точки зрения) должны работать с несколькими различными устройствами ввода-вывода в одном потоке? Следуйте вечным принципам хорошей архитектуры программного обеспечения и, по крайней мере, тестируйте классы со сложной логикой.
  • Вы студент (самоучка, как я или кто-то другой), который хочет изучить вечные принципы хорошей архитектуры программного обеспечения, очень хорошо создав небольшое приложение? Давай, малыш / сэр / мадам; Если вы не можете следовать этим правилам в небольшом приложении, какого черта вы предполагаете, что можете следовать им в сложном приложении?
  • Вы преподаватель программного обеспечения и ожидаете, что чрезвычайно большое количество людей будет учиться на вашем коде и внимательно его изучать? Что ж, я знаю, что буду делать.

Социальные сети | Служба поддержки

Эту статью написал Райан Майкл Кей. Я программист / инженер-самоучка, создающий образовательный контент по широкому кругу тем на самых разных платформах. Лучший способ поддержать меня - подписаться на меня на различных платформах и присоединиться к моему сообществу разработчиков (у нас сотни участников!):

Объявления:
https://www.facebook.com/wiseassblog
https://twitter.com/wiseAss301

Руководства и курсы:

Бесплатные уроки, интерактивные вопросы и ответы, живое программирование:
https://www.youtube.com/channel/UCSwuCetC3YlO1Y7bqVW5GHg

Программирование рабочего стола Java с JavaFX (средний уровень) - https://skl.sh/31pzCa1

Полное введение в программирование на Java для новичков (начинающий - средний) - https://skl.sh/3fZbjos

Приложения для Android с Kotlin и Android Studio (для начинающих) - https://skl.sh/2ZU6ZT9

Разработка материалов для Android с использованием Kotlin (средний уровень) - https://skl.sh/2OrwrYZ

Подключиться:

Группа Discord - https://discord.gg/2XVvCW

Slack Group - https://join.slack.com/t/wiseass/shared_invite/zt-3p7p67p1-ZEvEhlrznbksmvzS0Qtunw

LinkedIn- https://www.linkedin.com/in/ryan-kay-808388114/