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

Но что на самом деле означает «большая картина» и почему это важно? В этой записи мы постараемся ответить на эти вопросы.

На мой взгляд, «общая картина» включает в себя осознание следующих вещей:

  • Цели, для достижения которых написан код, включая связанные с ними бизнес-ценности
  • Стандарты, включая рекомендации
  • Форма и масштаб всей системы, частью которой является код

Вышеупомянутые три основных направления связаны с четвертым, то есть взаимодействием с пользователем, которое охватывает остальные три.

Мы постараемся изучить каждый из них и для начала поговорим о стандартах.

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

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

Основная цель соблюдения стандартов заключается в том, чтобы все работало должным образом (качество кода) и могло быть развернуто, когда ожидается (предсказуемость процесса).

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

Тем не менее, задавать «правильные» вопросы, основанные на критическом анализе, — это первое, что нужно предпринять, пытаясь понять целые системы. Мы удвоим это и посмотрим, как это может помочь в написании лучшего кода.

Итак, давайте начнем.

«Правильные» вопросы

Может быть столько разных вопросов, которые можно задать по любому фрагменту кода. Ответы могут вызывать вопросы в ответ на вопросы и новые вопросы в ответ на эти вопросы.

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

  • Кто будет использовать функционал?
  • Что они будут с этим делать?
  • Когда и где они получат к нему доступ?
  • Какую проблему пытается решить? Например, зачем им это нужно?
  • Как это должно работать? Если детали отсутствуют, полезно разбить этот вопрос на два отдельных вопроса:

а) Что должно произойти, если он успешно выполнится?

b) Что должно произойти, если выполнение завершится ошибкой?

Получение дополнительной информации о системе в целом обычно начинается с таких простых вопросов, как следующие:

  • С какими другими частями системы взаимодействует этот код?
  • Как он взаимодействует с ними?
  • Почему что-то делается именно так? Это лучший способ сделать это?

Вы должны относиться ко всему скептически. Именно тогда вы также начнете узнавать болевые точки существующей системы.

Определив все движущиеся части, подумайте о сценариях «Что произойдет, если…» — это хороший способ определить потенциальные точки, в которых что-то может сломаться, или определить риски и опасные взаимодействия. Вы можете задать такие вопросы, как следующие:

  • Что произойдет, если этому аргументу, который ожидает число, будет передана строка?
  • Что произойдет, если это свойство не является ожидаемым объектом?
  • Что произойдет, если какой-то другой объект попытается изменить этот объект, в то время как он уже изменяется?

Всякий раз, когда на один вопрос был дан ответ, просто спросите: Что еще? Это может быть полезно для проверки того, является ли текущий ответ достаточно полным.

Ответы на вышеупомянутые вопросы помогут вам писать более качественный код.

Давайте посмотрим на этот процесс в действии.

Чтобы обеспечить некоторый контекст, пишется новая функция для системы, которая отслеживает минеральные ресурсы на сетке карты для трех ресурсов: золота, серебра и меди. Местоположение сетки измеряется в метрах от общей исходной точки, и каждое местоположение сетки отслеживает число с плавающей запятой от 0,0 до 1,0, которое указывает, насколько вероятно, что ресурс будет найден в квадрате сетки. Набор данных развития уже включает четыре узла по умолчанию — в (0,0), (0,1), (1,0) и (1,1) — без значений, как показано ниже:

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

Константы, исключения и функции для различных целей уже существуют:

  • node_resource_names: содержит все имена ресурсов, с которыми имеет дело система, и может рассматриваться как список строк: [‘gold’, ‘silver’, ‘медь’]
  • NodeAlreadyExistsError: исключение, которое будет вызвано, если будет предпринята попытка создать MapNode, который уже существует.
  • NonexistentNodeError: исключение, которое будет вызвано, если будет сделан запрос для MapNode, который не существует.
  • OutOfMapBoundsError: исключение, которое будет вызвано, если будет сделан запрос для MapNode, которому не разрешено существовать в области карты.
  • create_node(x,y): создает и возвращает новый MapNode по умолчанию, регистрируя его в глобальном наборе данных узлов в процессе.
  • get_node(x,y): находит и возвращает MapNode в указанном (x, y) месте с координатами в глобальном наборе данных доступных узлов.

Разработчик делает первоначальную попытку написать код для установки значения для одного ресурса в данном узле в рамках проекта. Полученный код выглядит следующим образом:

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

SetNodeResource(0,0,None,'gold',0.25) print(get_node(0,0)) SetNodeResource(0,0,None,'silver',0.25) print(get_node(0,0)) SetNodeResource(0, 0,Нет,'медь',0,25) print(get_node(0,0))

Результаты представлены в следующем выводе:

По этим меркам в коде и его функциях нет ничего плохого.

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

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

  • Кто будет использовать эту функцию? Эта функция может быть вызвана приложением, используемым, скажем, горнодобывающей компанией. Компания, вероятно, не будет использовать его часто, но если они увидят очевидные признаки месторождения во время опроса, они должны войти в систему со 100% уверенностью в том, что найдут ресурсы в этом месте сетки; в противном случае они оставят рейтинг ресурса совершенно в покое.
  • Что они будут с ним делать? Учитывая базовые требования (установить значение для одного ресурса на заданном узле) и предыдущий ответ, кажется, что на него уже был дан ответ.
  • Когда и где они могут получить к нему доступ? Через библиотеку, используемую приложением. Никто не будет использовать его напрямую, но он будет интегрирован в это приложение.
  • Как это должно работать? На этот вопрос уже был дан ответ, но возникает вопрос. Будет ли когда-нибудь необходимость добавлять более одного рейтинга ресурсов одновременно? Это, вероятно, стоит отметить, если есть хорошее место для его реализации.
  • С какими другими частями системы взаимодействует этот код? Здесь мало что не очевидно из кода, в котором используются объекты MapNode, ресурсы этих объектов и функция get_node.
  • Что произойдет, если будет предпринята попытка изменить существующий MapNode? С кодом в том виде, в котором он был изначально написан, это ведет себя так, как ожидалось. Это счастливый путь, для обработки которого был написан код, и он работает.
  • Что произойдет, если узел еще не существует? Тот факт, что существует определенная ошибка NonexistentNodeError, является хорошим признаком того, что по крайней мере для некоторых операций карты требуется, чтобы узел существовал, прежде чем они могут быть завершены. Выполните быстрый тест, вызвав существующую функцию, как показано ниже.

SetNodeResource(0,6,Нет,'золото',0,25)

Предыдущая команда приводит к следующему:

Это результат, потому что данные разработки еще не имеют MapNode в этом месте.

  • Что произойдет, если узел не может существовать в заданном месте? Определена ошибка OutOfMapBoundsError. Поскольку в данных разработки нет узлов за пределами границ, и код в настоящее время не может пройти мимо того факта, что узел за пределами не существует, нет хорошего способа увидеть, что произойдет, если это произойдет. пытался.
  • Что произойдет, если z-значение в данный момент неизвестно? Поскольку функция create_node даже не ожидает z-значения, и экземпляры MapNode имеют один, существует реальный риск того, что вызов этой функции на существующем узле перезапишет существующее значение z-высоты. В долгосрочной перспективе это может стать критической ошибкой.
  • Соответствует ли это всем применимым стандартам разработки? Без каких-либо подробностей о стандартах, вероятно, будет справедливо предположить, что любые определенные стандарты, вероятно, будут включать, как минимум, следующее:

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

##### По крайней мере, некоторые усилия по документации, которых нет.

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

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

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

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

  • Он не предполагает, что узел всегда будет доступен.
  • Если запрошенный узел не существует, он создает новый для работы, используя существующую функцию, определенную для этой цели.
  • Не предполагается, что каждая попытка добавить новый ресурс будет успешной.
  • Когда такая попытка терпит неудачу, она вызывает ошибку, которая показывает, что произошло.

Все эти дополнительные элементы являются прямым результатом вопросов, заданных ранее, и принятия осознанных решений о том, как поступать с ответами на эти вопросы. Именно в таких результатах проявляется разница между мышлением программиста и инженера-программиста

Разработка программного обеспечения — это больше, чем просто написание кода. Опыт; внимание к детали; задавать вопросы о функционировании кода, взаимодействии с остальной системой и так далее; являются важными аспектами перехода от программирования к мышлению инженеров-программистов. Время, необходимое для приобретения опыта, можно сократить, возможно значительно, просто задав правильные вопросы.