97 этюдов для программистов. Опыт ведущих экспертов — страница 12 из 41

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

Повторение в логике указывает на необходимость абстракции

Повторения в логике бывают разных видов. Очень легко обнаруживаются и исправляются случаи копирования и вставки конструкций if-then и switch-case. Многие шаблоны проектирования явным образом направлены на сокращение или удаление дублирования логики в приложении. Если объект требует выполнения некоторых действий, прежде чем его можно использовать, полезно применить шаблон Abstract Factory или Factory Method. Если у объекта много разных вариантов поведения, полезно организовать их с помощью шаблона Strategy, а не больших структур if-then. Фактически само создание шаблонов проектирования является попыткой сократить расходование сил, затрачиваемых на решение стандартных проблем и их обсуждение. Кроме того, применение принципа DRY к структурам, таким как схемы баз данных, приводит к нормализации.

Дело принципа

Другие принципы программного обеспечения также связаны с DRY. Принцип «один и только один раз» (Once and Only Once), который применим только к функциональному поведению кода, можно рассматривать как подмножество DRY. Принцип «открыт/закрыт» (Open/Closed), гласящий, что «элементы программного обеспечения должны быть открыты для расширения, но закрыты для модификации» на практике действует только тогда, когда выполняется принцип DRY. Аналогично на DRY основан известный «принцип единственной ответственности» (Single Responsibility Principle), который требует, чтобы у класса была только одна причина изменения.

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

Этот код не трогать!Кэл Эванс

С каждым из нас такое когда-нибудь да случалось. Ваш код загружен на промежуточный (staging) сервер для системного тестирования, и руководитель отдела тестирования сообщает вам, что есть проблема. Вы сразу готовы ответить: «Дайте-ка я быстро все исправлю: я знаю, в чем дело».

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

В большинстве случаев, если речь идет о веб-разработке, архитектуру можно разбить на следующие части:

• Локальная разработка и модульное тестирование на машине разработчика

• Сервер разработки, где проводится автоматическое или ручное интеграционное тестирование

• Промежуточный (staging) сервер, где команда контроля качества и пользователи осуществляют приемочное тестирование

• Боевой (production) сервер

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

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

Администратору промежуточного сервера следует упаковать и перенести код на промежуточный сервер, где с ним будет работать группа контроля качества. Подобно тому как разработчики не должны иметь доступ за пределы сервера разработки, группа контроля качества и пользователи не должны выходить за пределы среды тестирования. Если версия готова к приемочному тестированию, собирайте версию и выкатывайте ее: не предлагайте пользователям «по-быстрому глянуть вот на это» на сервере разработки. Помните, за исключением ситуаций «один воин в поле», на сервере есть код и других авторов, которые могут быть не готовы к показу его пользователям. Ответственный за выпуск версий — единственный человек, у которого должен быть доступ к обоим серверам.

Ни в коем случае — ни при каких обстоятельствах — разработчик не должен быть допущен к боевому серверу. Если возникнут проблемы, команда поддержки должна либо справиться с ними сама, либо предложить вам внести исправления. Когда вы сохраните исправление в репозиторий, команда поддержки возьмет «заплатку» оттуда. Кое-какие ужаснейшие осложнения на проектах с моим участием возникали из-за того, что кое-кто (догадайтесь, кто) нарушил это правило. Если приложение сломалось, боевой сервер — не место для внесения исправлений.

Инкапсулируйте поведение, а не только состояниеЭйнар Ландре

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

Модули и пакеты решают задачи инкапсуляции крупного масштаба, в то время как классы, подпрограммы и функции призваны решать те же задачи на более низком уровне. За годы работы я обнаружил, что из всех видов инкапсуляции тяжелее всего программистам дается инкапсуляция в классах. Нередко встречается класс, единственный метод main которого имеет 3000 строк, или же класс, в котором есть только методы set и get для его элементарных атрибутов. Такие примеры показывают, что разработчики этих классов не вполне освоили объектно-ориентированное мышление и не умеют воспользоваться мощью объектов как моделирующих конструкций. Для тех, кто знаком с терминами POJO (Plain Old Java Object — простой Java-объект в старом стиле) и POCO (Plain Old C# Object или Plain Old CLR Object), замечу: изначально они выражали возврат к основам ООП как парадигмы моделирования — понятным и простым, но не тупым объектам.

Объект инкапсулирует как состояние, так и поведение, причем поведение определяется фактическим состоянием. Возьмем объект «дверь». У него четыре состояния: открыта, закрыта, открывается, закрывается. Он предоставляет две операции: «открыть», «закрыть». В зависимости от состояния операции «открыть» и «закрыть» ведут себя по-разному. Это неотъемлемое свойство объекта делает процесс проектирования концептуально простым. Все сводится к двум простым задачам: назначению и делегированию обязанностей различных объектов, включая и протоколы межобъектного взаимодействия.

Лучше всего показать, как это работает, на примере. Допустим, у нас есть три класса: Customer (Покупатель), Order (Корзина) и Item (Товар). Объект Customer — естественный источник правил проверки платежеспособности и определения максимальной суммы, доступной для списания. Объект Order знает, какой Customer с ним связан, так что операция addItem (добавитьТовар) делегирует фактическую проверку платежеспособности методу customer.validateCredit(item.price()) (покупатель.проверитьПлатежеспособность(товар.цена())). Если постусловие метода не выполнено, можно сгенерировать исключение и отменить покупку.

Менее опытные в ООП разработчики иногда решают обернуть все бизнес-правила в один объект, который часто называют OrderManager (МенеджерЗаказов) или OrderService (СлужбаЗаказов). В такой архитектуре объекты Order, Customer и Item, по сути, служат табличными типами. Вся логика выводится из классов и увязывается в одном большом процедурном методе, содержащем множество конструкций if-then-else. В методы такого рода легко вкрадываются ошибки, и сопровождать их почти невозможно. Почему? Потому что инкапсуляция нарушена.

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

Числа с плавающей запятой недействительныЧак Эллисон

Числа с плавающей запятой не являются «действительными числами» в математическом смысле, хотя в некоторых языках программирования, например в Pascal и Fortran, носят название таковых (real). Действительные числа имеют бесконечную точность, и потому они непрерывны и не подвержены искажениям. Числа с плавающей запятой имеют ограниченную точность, а потому конечны и похожи на «непослушные» целые, так как неравномерно распределены по всему своему диапазону.

Чтобы проиллюстрировать это, попробуйте присвоить 2147483647 (самое большое 32-разрядное целое со знаком) 32-разрядной переменной типа float (скажем, x), а потом напечатайте его. Вы увидите 2147483648. Теперь напечатайте x - 64. Результат — снова 2147483648. Теперь выведите x - 65, и вы получите 2147483520! Почему? Потому что промежуток между соседними float составляет в этом диапазоне 128, и операции с такими числами округляются до ближайшего числа с плавающей запятой.

В стандарте IEEE числа с плавающей запятой имеют фиксированную точность и записываются как числа по основанию 2 в научной нотации: 1.d1d2…dp1 х 2e, где p — это точность (24 для типа float, 53 для типа double). Интервал между двумя последовательными числами — величина 2