Pull to refresh

Comments 45

UFO just landed and posted this here
Самое печальное — это когда заказчик против использования third-party либ, а PM\TeamLead'у нафиг не надо его переубеждать, что мол 21й век на дворе, LGPL давно есть и все хорошо, прекрасная маркиза
UFO just landed and posted this here
Я ничего такого не предполагаю. Я говорю лишь о том, что при прочих равных условиях я выберу простое решение, которое будет понятно всем и каждому, вместо навороченного решения, которое даже мне будет непонятным через пару дней.

>>… ключевым элементом успеха в enterpirse, является глубокие знания в прикладной области…

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

Как всегда рулит прагматизм; понимание задачи очень важно, не спорю, но без нормального отношения к дизайну и коду тоже успеха не добиться.
UFO just landed and posted this here
Технический долг проекта столь велик, что добавление новой фичи стоит огромных усилий, а количество вносимых дефектов исчесляется десятками. Вопрос в том, сколько еще ждать успеха в моем случае?


В Вашем случае как и в моем, к сожалению — никогда. Я сам работаю с легаси системой одного топ 10 международного банка. Когда 99% времени уходит на поиск области изменения, анализ прикруток и 1% на вставку новой строчки кода и сборку.


Как то сильно пессимистично. У меня тоже клиенты банки :) Без ООП и повторного использования — это конечно труба. Без деталей тут говорить конечно демагогия. Может мы попробуем сформулировать основные проблемы?

Тут первый вопрос в том тратите ли вы время на рефакторинг ПЕРЕД внесением новой фичи? Основная проблема на мой взгляд, не в предметной области (её можно сильно и не знать, правда при условии, что есть хороший постановщик задачи/выполняющий анализ предметной области). Нужно как бы расставить в нужных местах «точки роста», позволяющие достаточно легко менять поведение.

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

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

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

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

А отверстия в стене, сделанные молотком, никуда не годятся.

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

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

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

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

Но вы не обращайте внимания, что я понимаю…
Поможет на первых порах статический анализ кода — как минимум укажет на копи-паст или сложные места, найдёт ворнинги и даже подскажут если со стилем что не так.
Не нужно путать отсутствие простоту с отсутствием дизайна. Это совсем не одно и то же. Плохой код, отсутствие тестов, дикая связность и фиговая архитектура, все это уж точно не из-за отсутствия обобщений на ранних этапах. Или вы думаете, что если бы вы начали с разработки фреймворка, то у меня есть сомнения, что было бы лучше.
А мне вот кажется, что как раз отсутствие как минимум размышлений о устойчивом фреймворке и есть не просто следствие фиговой архитектуры — а указывает просто на её отсутствие. В моем понимании — нет фреймворка — нет архитектуры, а значит и нет дисциплины программирования.
У нас, видимо, очень разные взгляды на то, что означает термин «архитектура» и «фреймворк».

Во-первых, рекомендую полистать Framework Design Guidelines, чтобы появилось ощущение того, что принципы, лежащие в основе фреймворков отличаются от стандартных принципов проектирования. Поскольку основная цель фреймворков — это их повторное использование, то там подходят очень по другому к публичному (точнее, в этом случае к «публикуемому» интерфейсу), юзабилити, производительности и т.д. Зачастую эти принципы настолько важны, что разработчики фреймворков могут жертвовать такими общепринятыми принципами, как SRP, только в угоду юзабилити или производительности.

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

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


Видимо именно так. Давайте попробуем разобраться. Слово «фреймворк» зачастую сильно вводит в заблуждение, наиболее точным русскоязычным аналогом будет «архитектурный каркас». Т.е. есть два вида повторного использования: (1) низкоуровневые функции, типа работы с файлами, базой данных, и т.д. (2) управляющий код, который управляет высокоуровневыми процессами обработки. В первом случае говорят о ядре, реализованном скажем в отдельной .dll библиотеки. Во втором говорят о каркасе — достаточно зарегулированном способе написания кода для него. Если в первом случае мы вызываем функции из .dll, то во втором каркас вызывает функции написанные нами.

Вот и вся разница. Концепция того в какой мере используется ядро+каркас+предметная область+контроль средств разработки = архитектура.

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

Теперь я хотел бы, что вы достаточно четко определили как вы это себе представляете.
Ок. Давайте проясним еще несколько моментов. Первое: определение термина фреймворк.
Framework is… " It is a collection of software libraries providing a defined application programming interface (API).
A software framework is a universal, reusable software platform used to develop applications, products and solutions."

Так что здесь разница не только в push и в pull-модели общения. Но даже если не в даваться в дебри, суть в том, что фреймворк по своему определению — это «reusable software platform», как ни крути, а это, в свою очередь значит, что: (1) его сложно разрабатывать, (2) его дорого поддерживать, (3) его нельзя разработать при разработке одного приложения, поскольку это будет противоречить пункту «reusable sotware platform».

Выбор фреймворка — это архитектурное решение безусловно, но не нужно передергивать мои слова, я говорил о «кастомном фреймворке». Мой поинт в том, что не нужно выдумывать фреймворк, когда вы только сели за разработку нового типа приложения, ведь до того, как вы напишите пару таких приложений вы не сможете сделать фреймворк по определению. И в большинстве случаев у команды просто не будет возможности наобобщать свои решения настолько, чтобы отточить его до фреймворка (хотя бывают, безусловно и исключения).
не нужно выдумывать фреймворк, когда вы только сели за разработку нового типа приложения


Тут я с вами согласен, ниже я писал

Если вы описываете процесс разработки для одного человека, который пришел учится в компанию после института — то я с вами почти согласен.

Но если в компании налажен процесс разработки, то он выглядит несколько иначе


«Ваше» определение фреймворка мне кажется не очень удачным, API в фреймфорке дело десятое. И еще есть ньюанс: "(3) его нельзя разработать при разработке одного приложения, поскольку это будет противоречить пункту «reusable sotware platform»."

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

Это общая вещь для множества форм-документов. Т.е. в приложении у нас таких может быть 10-20 шт. и начать писать для этого часть фреймфорка мы уже сможем после третьей-четвертой — ведь опыт у нас уже есть !? И начнется этот фреймфрк с того, что мы захотим контролировать показ форм, и как правило для этого создадим фреморковский класс Менеджер-форм. Этим измениться дисциплина вызова всех форм, теперь программист будет пользоваться этим классом, а не прямо вызывать форму.

Со временем (где-то в средине разработки) окажется, что например в C#, мы захотим пользоваться не только WF, но и WPF. А WPF окажется, что не поддерживает многооконную дисциплину. Поэтому менеджеру форм придется часть реализации совмещения WF и WPF скрыть от пользователя-программиста.

А теперь подумаем, что означает не начинать создавать фреймфорк, в данном случае менеджер форм. Каждый программист будет реализовывать функции показать, редактировать, новый по своему, причем если он не сильно дисциплинирован, то даже у одного будет отличаться то как это он реализует для разных форм. И я уже не говорю о том, сто вариантов совмещения WF и WPF множество. Но ведь программисту не важно, чтобы это работало всегда и насколько это будет эффективно — он не будет тратить время, он выберет частный случай, которые ему подходит, а скорее всего первый попавшийся (ведь начальник ему сказал реализуй форму-документ — это его цель, поэтому на побочные он потратит на порядок меньше времени, т.к. платят ему за другое). В итоге проект уже обрастет бородой, и преобразовать его в стройную идею будет куда сложнее, чем если бы не делать зачатки фреймфорка.
Увы, без покрытия тестами увязнуть в «простом» коде можно очень быстро. Я бы к этому и перешёл в вашем проекте, пока клубок не завязался ещё сильнее.
Не знаю, может я чего-то не понимаю, но на практике покрытие тестами сделать практически не реально. Тут получается так, что даже наоборот их наличие тянет тебя к земле — надо постоянно поддерживать тесты, вместо того, чтобы рефакторить код. И как правило поддержка актуальности тестов съедает на порядок большее время, чем более аккуратный постепенный рефакторинг с последующим тестированием «черного ящика». Да, периодически тестировщикам приходится ловит якобы одни и те же ошибки, но они как правило выявляют различные варианты использования GUI, которые так или иначе тестами не покроешь. И тогда спрашивается — а ради чего?
Если тестируется абстракция (т.е. то, что должен делать класс, его контракт), а не то имплементация (то, как он реализован), то тесты на пустом месте не ломаются. Они в таком случае ломаются только тогда, когда нарушена логика, которую они призваны защищать.

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

З.Ы. Если у вас в проектах не удавалось добиться адекватного покрытия (я, кстати не говорю за 100%), то это не значит, что это невозможно, это говорит лишь о том, что вы не умеете это дело готовить.
Ок, мы по крайней мере поняли какими признаками обладают плохие тесты, и согласились, что их нет смысла делать. Теперь осталось понять, а существуют и могут ли существовать хорошие тесты.

Вы считаете, что надо покрыть тестами каждый класс, и при этом его public часть. Действительно. якобы наличие у класса четкой задачи должно приводить к замкнутому конктракту, который класс должен выполнять. Но давайте подумаем. Вот вы говорите тут, что мы не имеем возможности во время разработки, что-то обобщать и выделять повторно используемые части. Так?

С другой стороны, что мы делаем когда наделяем классы задачами/ответственностью за что-либо? В слое ответственном за предметную часть, будут такие классы как Человек, Организация, Сделка, и т.д. Но будут ли они полны? Думаю нет, они всегда будут специфичны для той задачи которую мы решаем, т.е. в зависимости от задачи мы сделаем ту или иную абстракцию. И будем её совершенствовать только по мере нашего понимания и углубления в задачу.

Т.е. тесты мы сможем писать тоже постепенно, ведь мы не знаем вначале какую полноценную абстракцию должен реализовать тот или иной класс. Мы двигаемся постепенно, аналогично тому как не можем (согласно вам) на этапе проектирования обобщать. И делаем это лишь после того как точно узнали какую абстракцию должен поддерживать класс. Но когда мы это знаем — зачем на к тому времени какие-то тесты? Зачем нам тогда что-то обобщать? Совершенно не зачем — мы так уже все написали и отладили.

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

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

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

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

А теперь взгляните на то, что такое хороший тест? Не является ли хорошие тест — зачатком обобщения для повторного использования? Не выполняет ли повторное использование роль тестов?


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

З.Ы. Фаулер десять лет назад писал о разнице между публичным и опубликованным: PublishedInterface

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

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

Так что тогда тестировать в таком случае?
Если писать тесты до написания кода, то всё станет через время на свои места. Они, да, сдерживают полет фантазии и обрезают крылья.
Но так и надо. Через время, такое сдерживание заставляет и к коду относиться иначе. Стараться писать его более кратко, более четко выражая мысль. А тесты по сути становятся для разработчика не тестами — а требованиями. Он уже интуитивно считает написание тестов — это написание требований, которым должен удовлетворять код. Тесты делают краткими и простыми. А когда проводят рефакторинг, как раз в этом и сила тестов — они падают. И их надо поправить.

Если бы их не было, то как-то стремно код менять. Что-то падает только в платоновском мире идей. А когда оно здесь упадет — неизвестно.

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

Есть такая ерунда: когда я не писал тесты, то мне казалось, что я крут — я мог полдня писать, написать кучу кода, кучу классов, и запускал — оно сразу делало всё что надо, при первом запуске. А когда начал писать тесты, то стабильно нахожу еще до коммита один-два бага.

Потом, я переписал и перелопатил и изменил логику всего движок и еще одну важную систему в текущей конторе. Эта система работала уже. Любые сбои дорого стоят. И приятно, когда я два раза выкатывал практически на ходу — и всё бесшовно.

И на тесты не так много времени ушло. И на поддержку на самом деле не так много уходит.
«так как нет тестов»

Эта фраза всё убила. Нет тестов — следовательно нет рефакторинга. Кстати, и не будет нормального рефакторинга, если нет тестов.

А если нет рефакторинга, то вообще никак не будет происходит не только преждевременное, но и своевременное обобщение.
UFO just landed and posted this here
Спасибо, хорошая статья. Хотя при чём тут ООП?
Побольше бы ссылок на то, как именно «ООП провалилось».
ООПкапец, однако.
Бензиновые авто тоже «провалились» — неэкологичные, опасные, многие пользуются не по назначению («друзьям показать, ЧСВ потешить»). Но что-то не вижу я миллиардов, разъезжающих на других видах транспорта =).
Хотя, безусловно, во многих случаях последнее таки удобнее.
Иногда бывают и более паталогические случаи: например, с самого начала за дело может взяться «архитектор» с непреклонным желанием построить свой «фреймворк» с блэкджеком и девицами когда еще нет четкого видения того, что нужно команде



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


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

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

Обобщать во время разработки — это нонсенс — никогда так не делайте, если кончаются сроки. Наоборот, допускаются любые «кривые» решения (за исключением дублирования кода). Но вот как только сроки отступили, начните новый проект с новым опытом, и начните с того, что сделайте рефакторинг уже сданного в эксплуатацию кода.
А как насчет вставить Обобщение между Проектированием и Разработкой?
Чтобы на очередной итерации обобщить нужные для реализации этого шага части системы исходя из спроектированных требований.
По сути в конце итерации мы хоть и знаем больше о системе, но пока еще не известно в какое русло в дальнейшем пойдет разработка. Но уже что-то пределываем, обощаем…
именно так теоретически и должно быть
Напомню, что данную «теорию» предложил Мейер, у которого с теорией все в порядке. К сожалению, не все мы можем сделать на этапе проектирования. Именно поэтому так рулит итеративная разработка.
«К сожалению, не все мы можем сделать на этапе проектирования.»

К сожалению, именно так — именно не все (а не «не всё»), этим и отличается программист от архитектора.
Я так понимаю, что архитектор (сори, но у нас должности аналогичные;) ) всегда может всЁ это (и обобщение тоже) сделать на этапе проектирования?

Это сделать сложно не только в силу слабости архитектора, но и потому, что очень часто решение меняет задачу. Как быть в этом случае?
«решение меняет задачу» — я не очень точно понимаю какой смысл вы вкладываете в эти слова, может пояснить подробнее, а лучше с примером.
Вставить обобщение между проектированием и разработкой сложно по нескольким причинам. Во-первых, эти этапы слишком сильно связаны. Хоть сейчас даже agile адепты признают важность архитектуры, но слишком уж формального этапа проектирования никто все равно не делает. Обычно на этом этапе рисуются диаграммы классов, расписываются типовые юз-кейсы, указываются контракты.

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

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

Кроме того, бывает что решение влияет на исходную задачу. Это тоже бывает, ведь в процессе решения заказчик и команда могут лучше понять, что же на самом деле нужно. Тогда попытка обобщить до этого момента просто провалится по определению.
Все это верно, когда вы говорите о слое предметной области. Ну и только. Причем такой предметной области с которой вы столкнулись впервые. Но кроме этого слоя есть ряд других технических слоев. Абстракция работы с базами данных, событиями, формами, стилистика ведения диалогов с пользователем, решение как реализовать идею Model-View-Controler и реализовывать ли её вообще. Все это и есть основы архитектуры. Если программисту сказать вот тебе язык программирования такой-то и делай как считаешь нужным. Все ставим точку — у вас в проекте просто нет и не будет архитектуры.

Конечно, архитектура не появляется у программиста с его первым проектом. Ну, и вообще-то не очень серьезно доверять человеку после института должность архитектора ПО. Архитектором ПО как правило становится не тот кто умеет рисовать красивые диаграммки на UML, а такой программист, который разработал ряд общих техник как программировать ту или иную типовую задачу (обозначим F) с переменной предметной областью (обозначим x). Т.е. у него есть опыт построения функций F(x).

Если вы описываете процесс разработки для одного человека, который пришел учится в компанию после института — то я с вами почти согласен.

Но если в компании налажен процесс разработки, то он выглядит несколько иначе. Архитектор задает и определяет F, а остальные программисты кодируют x, y, z… И тогда задача архитектора состоит в том, чтобы его F соответствовала потребностям x, y, z, но так чтобы по возможности программист выполнял ряд ограничений, не нарушающих согласованность между x, y, z. Именно эту роль должна играть архитектура, и она не может быть создана постафактум. Она или есть вначале или её просто пока нет. Ожидать что принципы архитектуры оценят и поймут все программисты — это не серьезно, им надо их просто выполнять… чего порой не любят программисты.
т.е. во время разработки надо возвратить код в русло архитектуры, разработанной во время проектирования архитектором. Но если с архитектурой, что-то не так — то не пытайтесь переделать работы архитектора — путь пашет сам, и указывает вам как сделать то или иное, т.е. только он имеет право исправлять дефекты архитектуры во время разработки. Именно это и будет его опытом для следующего проекта.
Так вот вопрос: как же можно достичь той заветной мечты, когда системы можно будет строить из готовых компонентов, не написав при этом ни строчки кода?
Уже можно
Мне кажется место этапа «Обобщение» не в конце текущей итерации, а после «Анализа требований» следующие итерации (или другого проекта). Похоже на рефакторинг для облегчения внесения изменений, только в общем случае охватывающий несколько проектов.

Не до конца понятно только, как это рисовать.
Sign up to leave a comment.

Articles