23:05
О плохо написанном коде
Точнее, изначально он, возможно, был написан и хорошо. Но потом пришлось добавить ещё одну функциональность. Ещё одну. Ещё. В итоге получается монстр. И чтобы понять, как он работает, приходится выписывать основные управляющие конструкции на бумажку (без спагетти-основных действий и без внутренних переходов) и рисовать связи между ними. Зрелище получается кошмарное, но менее кошмарное, чем сам монстр. Теперь можно продолжить работу. И приделать что-нибудь ещё.
То есть речь именно о плавно наращиваемом функционале. Иногда по-другому не сделаешь.
Можно ли выйти из этого порочного круга? По-моему, единственный выход -- переписывать каждый раз весь кусок программы с нуля, указав новую функциональность в ТЗ. Иначе миллиона флажков и доп переменных типа "номер итерации в пятом режиме работы" не избежать.
А вот чего следует избегать даже при таком монстре -- повторного нелогичного использования переменных. Допустим, у функции два режима работы -- расчёт для длины пружины и для скорости прокачки воды. Вот не следует делать, чтобы в режиме для пружины переменная обозначала диаметр проволоки, из которой изготовлена пружины, а в режиме для скорости прокачки воды -- эту самую скорость прокачки воды. Это утрированный пример, я его привожу для очевидности.
То есть речь именно о плавно наращиваемом функционале. Иногда по-другому не сделаешь.
Можно ли выйти из этого порочного круга? По-моему, единственный выход -- переписывать каждый раз весь кусок программы с нуля, указав новую функциональность в ТЗ. Иначе миллиона флажков и доп переменных типа "номер итерации в пятом режиме работы" не избежать.
А вот чего следует избегать даже при таком монстре -- повторного нелогичного использования переменных. Допустим, у функции два режима работы -- расчёт для длины пружины и для скорости прокачки воды. Вот не следует делать, чтобы в режиме для пружины переменная обозначала диаметр проволоки, из которой изготовлена пружины, а в режиме для скорости прокачки воды -- эту самую скорость прокачки воды. Это утрированный пример, я его привожу для очевидности.
01.06.2012 в 01:02
01.06.2012 в 19:18
Специалисты говорят "всё должно быть продумано", но лучше всего выходит, когда двигаешься от задачи к задаче.
Специалисты говорят "никаких глобальных переменных", но лучше всего выходит как раз с ними (и вообще, когда части кода друг о друге не знают).
Чем меньше запутываешь клубок, тем легче его сматывать каждый раз по-новому.
01.06.2012 в 22:13
Приведу два с половиной примера из своей практики:
Мне пришлось писать драйвер USB-конроллера для компьютера без ОС (т.е. для "голого железа"). Я потратил примерно 40% всего времени на проектирование системы. Драйвер писался на Си. Тем не менее в нём появились некоторые отзвуки ООП, сам драйвер был оформлен как своего рода автомат. Архитектура получилась очень удачной. ТЗ менялось дважды. При этом довольно сильные изменения в ТЗ не привели к "архитектурному расползанию". Секрет в чём? В основе системы лежал абстрактный механизм, на базе которого реализовалась система. Этот механизм и послужил тем самым уровнем абстракции. После его создания добавление промежуточного шага становилось уже тривиальным, а изменение логики работы --- довольно простым.
// ssvda (tbc)
01.06.2012 в 22:24
Сейчас она переписывается уже "с чувством, с толком, с расстановкой". В основу опять заложен некий механизм. И опять система получается гибкая и пластичная. Очень спокойно реагирует на изменения в ТЗ. А всё почему? Потому что за счёт введения кучи новых классов-прослоек (пишется на C++, процентное содержание таких классов около 40%) которые позволяют почти полностью абстрагироваться от всего на свете (^_^). В результате получаются хорошо спланированные интерфейсы, в которых каждый уровень абстракции связан только с уровнем на один выше и ниже.
А рецепт, имхо, всегда один: «делайте так, как будто вам всё придется переделывать» (и ведь скорее всего придётся ^_^).
02.06.2012 в 01:05
1. Простых вещей.
2. Классов-прослоек к ним (потому, что старые не умеют прослаивать часть функциональности)
3. Новых версий старых прослоек (потому, что теперь им нужны заглушки по новой функциональности)
4. Новой версии общей архитектуры (потому, что что-то из новых прослоек несовместимо)
И на двадцатом часу понимаешь, что программист-наколенщик, который врезал бы новую функцию в тело программы намертво за полчаса, где-то в чём-то тебя всё-таки обошёл. И будет обходить ещё пять лет, пока его программа не рухнет-таки под собственным весом. Но твоя к этому времени, скорее всего, тоже загнётся от сложности абстракций
Мне нравится, например, как сделан вордпресс. Вот полный текст плагина, который в принятых из RSS постах (сама эта функциональность - чужой плагин) заменяет BR на новые строки. Если б это была архитектурная астронавтика, для той же цели пришлось бы включать пять хедеров, объявлять реализацию интерфейса IFeedWordPressPostFilter, реализовывать по дефолту 15 ненужных его функций и одну нужную, и всё это ломалось бы, когда сам FeedWordPress не установлен.
А тут - одна строчка и одна функция. При этом ни один из других модулей вордпресс о новичке и не подозревает. Такие вещи и писать, и расширять приятно.
02.06.2012 в 01:57
А вообще, всё ведь ещё зависит от архитектуры системы. Например, cms, драйвер ядра, программу для математических расчётов, программу для управления процессом и программу для работы с данными я бы скорее всего стал бы писать по-разному.
Выбор именно такой архитектуры для программы продиктован тем, что в ней много физических сущностей (основные классы), которые должны друг с другом взаимодействовать.
А вот про прослойки к прослойкам не надо. Этак можно любую идею до абсурда довести =)
Вот полный текст плагина
Так а что такое плагин в данном случае, как не абстракция? Просто другого уровня и выраженная в других терминах языка =)
02.06.2012 в 16:22
1. Надо предположить, какая похожая функциональность может потом потребоваться.
2. Надо выделить общее.
3. Надо встроить в архитектуру общее.
4. Надо сделать частную реализацию общего для текущей задачи.
Проблем с этим две, и обе страшные. Во-первых, вместо одного шага делаешь четыре. Во-вторых, на первом же шагу занимаешься гаданием, а гадание почти никогда не работает. Любая абстракция течёт, любые попытки угадать, как будет развиваться система, приводят к построению лишних частей "на будущее", которые на самом деле не пригодятся.
Поэтому возникает обратное намерение: раз мы не можем угадать, как наши будущие клиенты будут использовать наши функции, давайте, мы не будем заранее их в этом ограничивать? Классические подходы в программировании предполагают не давать клиентам ничего, кроме того, что заранее задумано им позволить. Реализация скрыта, доступ только через ограниченный набор функций, каждая из которых находится под контролем самого объекта. А тут наоборот - разрешено всё, что не запрещено. И минимум регулировщиков, которые помогают всем клиентам не столкнуться друг с другом (и вообще не замечать друг друга).
02.06.2012 в 22:32
(1) Очень часто имеется набор уже проверенных и отработанных способов абстрагирования. Если ими пользоваться как своеобразными стандартами де факто, то все четыре пункта заменяются одним. Я не спорю, что построение абстракций требует некоторого понимания задачи и осознания того в каких направлениях задача может развиваться. Тут мне видятся два решения:
(1.1) Продумать (почти) всё с самого начала. Это реально, но очень уж сложно.
(1.2) Начать писать применяя хорошо отработанные абстрактные подходы на высоких уровнях. Постепенно спускаться всё ниже и уже на низких уровнях выбирать относительно локальные абстракции по 4 пунктам.
(2) Но даже не это самое главное. Кто сказал, что интерфейсы и абстракции не могут меняться в процессе совершенствования системы? Дайте я брошу в него камень =) Они не только могут, но и наверняка поменяются. Поэтому:
(2.1) Мы вводим, как я их называю, основополагающие абстракции. Чаще всего это некие классы, представляющие физические объекты, системы, понятия и т.п. Причём я говорю не только о ООП-языках. Например, файл --- тоже абстракция т.к. это понятие, скрывающее детали хранения информации на устройстве памяти. При этом файловый интерфейс не обязан быть ОО.
(2.2) А дальше ОПАСНОСТЬ. Если мы сейчас начнём непосредственно связывать абстракции, то они будут завязаны на свои интерфейсы и реализации. По сути это естественно, но это усложняет изменение интерфейсов, усложняет изменение принципов действия. Поэтому в ряде случаев (не всегда!) мы добавляем абстракционный клей --- некую прослойку между двумя абстрактными интерфейсами. Теперь интерфейсы лучше друг от друга изолированы, так что мы упрощаем себе жизнь на случай перестройки программы. ИМХО, это есть гуд. По крайней мере по моим наблюдениям в проектах, построенных по такому принципу, архитектурные изменения проходят проще.
(2.3) А вот писать всё это 33 раза задолбаешься. Тут на помощь приходят скрипты для генерации исходного кода заготовок.
03.06.2012 в 09:38
Вот-вот! В этом и проблема!
Классический подход "разрешено только то, что задумано" неявно предполагает, что вся программа пишется в один шаг, и каждый компонент знает, что нужно разрешить. Если ТЗ меняется, перепроектируется вся программа (в идеале).
С помощью прослоек Титаник делится на отсеки, которые можно перепроектировать по одиночке. Но это полумера. Всё равно, каждый раз, как меняется мелочь, приходится переделывать всё до ближайшей переборки. Предположение о написании одним махом никуда не делось.
Прослойки и интерфейсы - это подпорки для исправления заведомо неудобной посылки: что программу можно спроектировать сразу.