ФЭНДОМ


ПРИНЦИПЫ ОО ДИЗАЙНА или всему, что я знаю о программировании, я научился у ДилбертаПравить

Автор Alan Knight, 2000
Перевод Андрея Собчука, 2003

Впервые статья была опубликована в "The Smalltalk Report".

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

Никогда не делайте работу, которую кто-то другой может сделать за васПравить

Такой совет хорош в любом случае, и, в частности, применим к ОО. Фактически, я считаю, его одним из основных принципов ОО. Мои обязанности, как объекта, очень четко определены, и так же они определены у моих коллег по работе. И если что-угодно является (или должно быть) их обязанностью, значит я не должен пытаться сделать это вместо них. Давайте рассмотрим конкретный пример:

 всего := 0.
 завод счета 
   делать: 
     [:каждый |
       (каждый статус == #оплачен и: 
         [каждый дата > начальнаяДата])
       еслиИстина: [всего := всего + каждый сумма]].

или:

 всего := завод счетаОплаченныеС: начальнаяДата.

В первом случае, мы запрашиваем список всех счетов завода, ищем сами те счета, которые нас интересуют и вычисляем сумму. Это большой кусок работы, и почти вся она не наша. Гораздо лучше использовать второй вариант, в котором мы просто просим, чтобы что-то было сделано, и получаем назад результат. Аналогом из реального мира, может быть такой разговор:

"Прошу прощения, Сидоров, но мне нужно знать сумму счетов, которые были оплачены в этом квартале. Нет, не утруждайте себя. Если Вы дадите мне ключи от своего кабинета, то я сам найду то, что мне нужно. Я не очень разбираюсь в Ваших делах, но это же не сложно? Я попытаюсь не напутать слишком много."

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

"СИДОРОВ! Мне нужна общая сумма всех проплаченных с начала квартала счетов. Нет, меня не интересуют детали твоей работы. Мне нужна общая сумма, и я ожидаю увидеть её на своём столе через пару миллисекунд."

Давайте рассмотрим пример попроще. Это очень частая ситуация:

 некто клиенты добавить: Клиент новый.

или:

 некто добавитьКлиента: Клиент новый.

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

Избегайте обязанностейПравить

Если "обязанности" это "кому придётся возится с работой", то очень важно избегать обязанностей. Отсюда есть несколько выводов:

  • если вы должны принять какие-либо обязанности, то старайтесь, чтобы они были как можно более размытыми;
  • с любыми своими обязанностями, попытайтесь передать реальную работу кому-нибудь еще.

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

Если же мы берём на себя обязанности, то очень важно сделать их как можно более нечеткими. Если нам повезёт, то подобная размытость поможет мне позже переложить работу на кого-либо еще. Даже если мне действительно прийдётся работать, то, возможно, я смогу уменьшить объём работ и никто этого не заметит. Например, я видел обязанности объектов сформулированные так:

"Поддерживать коллекцию жалоб, которые должны быть удовлетворены".

Слишком подробно. Моя работа не в том, чтобы поддерживать коллекцию, а в том, чтобы при необходимости сообщить, какие жалобы должны быть удовлетворены. Внутри может использоваться коллекция, возможно данные запрашиваются у нескольких других объектов, а возможно, все данные "вбиты" в код или вычисляются "на лету" через "Жалобы всеЭкземпляры выбрать: 1..." И не зависимо от того, какой вариант я выберу, не должно быть никакого влияния на мои обязанности.

Я предпочитаю перефразировать подобные обязанности так:

"Знать, что ..."

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

"Иметь возможность сообщить, что ..."

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

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

Откладывайте решенияПравить

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

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

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

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

Управляющие не должны делать реальную работуПравить

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

Я уверен, что управляющие могут помогать, но очень важно различать хороших и плохих управляющих. Например, представим программу, в которой большинство классов представляют из себя "структуры" (объекты, из поведения имеющие только методы доступа к данным). Реальная работа происходит в управляющем классе, который манипулирует этими "структурами", имея полный доступ ко всем их данным. Такая программа, является написанной в процедурном стиле и замаскированна под ОО программу. Такой управляющий объект полностью нарушает основные принципы ОО, так как он пытается всё сделать сам. С другой стороны, рассмотрим оконный класс, такой как VisualWorks ApplicationModel (ПрикладнаяМодель) или WindowBuilder WbApplication (WbПриложение). Эти управляющие объекты координируют взаимодействие между виджетами пользовательского интерфейса и доменной моделью. Они работают, как жизненно важный "склеивающий" слой (хотя я предпочитаю представлять их как трубопровод) и без них было бы очень тяжело получить хороший дизайн. Этот паттерн выражен менее четко в VisualAge, так как склеивающий слой размазан по ряду различных Частей (классы Part), но он там есть.

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

"Реальный мир это проблема. Почему мы должны в точности имитировать его?"

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

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

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

Другая сложность в том, что слово "Управляющий" иногда добавляют к имени класса, хотя этот класс абсолютно им не является. В одной из дискуссий в comp.object Роберт Коухэм (Robert Cowham, e-mail: cowhamr@logica.com) описывал класс УправляющийПолитикойДисконтирования (DiscountPolicyManager), и беспокоился о том, нужно ли вводить управляющий объект, хотя, похоже, он и делает дизайн более ясным. Описание было таким:

"УправляющемуПолитикойДисконтирования передаётся, например, Счет-фактура, и он определяет, какой дисконт применим к этому Счету-фактуре (используя методы Счета-фактуры) и затем использует метод Счета-фактуры, чтобы добавить к нему дисконт."

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

Преждевременная оптимизация оставляет всех недовольнымиПравить

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

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

Но это не самое худшее. Преждевременная оптимизация напрямую нарушает принцип откладывания решений. Оптимизация часто вызывает, например, такие мысли, как "если ограничим это целыми в диапазоне от 3 до 87, то сможем использовать МассивБайт (ByteArray) и заменить поиск в словаре на доступ к массиву". Проблема в том, что мы сделали наш код менее понятным, и ограничили гибкость нашей программы. Всё может идти хорошо большую часть времени, но другие занятые в проекте люди могут быть недовольны.

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

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

Другие правила, по которым житьПравить

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

Попытайтесь не интересоваться - начинающие программисты на Smalltalk-e часто сталкиваются с трудностями, пытаясь узнать все особенности работы чего-либо до того, как это использовать. Это значит, что пройдёт много времени, прежде чем они овладеют Transcript show: 'Hello World' . Одна из наибольших трудностей в ОО — это научиться на вопрос "Как это работает" отвечать "Я не знаю".

Просто делай! - отличный лозунг для проектов, которые страдают от аналитического паралича, то есть невозможности ничего делать, кроме создания отчетов и диаграмм на все случаи жизни.

Избегайте давать обязательства - еще один вариант принципа откладывания решений, который может понравиться молодым или неженатым программистам.

Это плохой пример, если он не работает - это пример пришел от "объектного человека" Дэвида Бака (David Buck, e-mail: david@simberon.com), который столкнулся с примерами и тестовыми методами, которые не изменялись с изменением кода. Не знаю, как применить это к реальной жизни, но, в любом случае, это хороший совет.

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

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

Оригинальная статья была опубликована на сайте "Smalltalk Cronicles" в марте 2000 года. Перевод Андрея Собчука, 2003

СноскиПравить

1)...

... allInstances select: ... — Перев.

2)...

Игра слов. В оригинале "... makes software hard". Software - ПО, hardware - оборудование, "soft" - мягкий, антоним к слову "hard" (тяжелый, твёрдый) — Перев.

3)...

Мне термин "непростых" не кажется понятным. Я бы сказал, что нормальным является выполнение вычислений связанных с представлением объекта. Например, для отображения суммы в тысячах, нужно исходное число разделить на тысячу. Но и с этим можно поспорить. — Перев.

Обнаружено использование расширения AdBlock.


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

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

Также на ФЭНДОМЕ

Случайная вики