Tag Archive for 'C++'

Программерское: Мой топ признаков плохого кода

Собрал тут свой собственный список признаков факапнутости кода.

На первом месте – классы со словом “Manager” в названии. Безоговорочный лидер в номинации “превратим любой код в страшные спагетти”. Самое ужасное в таких классах это то, что делаются они из самых лучших побуждений. Впрочем, так всегда – самые кошмарные деяния совершаются под прикрытием лозунга “мы же хотим тебе лучшего”. Я специально не пишу о внутренностях этих классов, само название “Manager” для меня уже достаточное основание к тому, чтобы код полетел в помойку.

Второе место достается небезопасным преобразованиям типов. Ну, это когда в коде появляется преобразование от класса-предка к классу-потомку. Вроде этого

class A;
class B: public A;

void DoSomething(A* ptr)
{
(B*)ptr->MethodOfB();
}

100% признак проявления идиотии в проектировании иерархии классов. Особый шик, когда подобный downcast встречается в одном из базовых классов – это уже индикатор того, что пора сушить весла. Исключений не бывает. Если же такой код появляется в процессе рефакторинга там, где ничего подобного до того не было, то сушить весла нужно отправлять аффтара этого рефакторинга.

Третье место – классы “универсальный всемогутор”.  Близкий родственник класса “Manager”, протащеный оным на ключевую позицию в проекте, способен отравлять жизнь двум-трем поколениям программистов, которым выпадет этот код поддерживать. Обычно характеризуются тучей публичных методов, никак не связанных по смыслу ни друг с другом, ни с названием и предназначением класса и не изменяющих и не использующих ни одного из свойств класса, но при этом зачастую имеющих тучу неявных связей друг с другом.

Четвертое место – преднамеренное нарушение энкапсуляции. Это когда в класс “бутылка пива” пытаются заодно вкрячить нечто, что вроде как должно научить экземпляры его автоматически упорядочиваться внутри класса “ящик пива”. Когда через неделю закономерно выяснится, что бутылки нужно упорядочивать в сикспаки, начинается любимый в народе аттракцион “аврал”. Есть вообще только один случай, когда объектам допустимо иметь какие-то знания о контейнере, в которые их будут складывать – это преднамеренно делается интрузивный контейнер. Во всех остальных случаях такой код – индикатор того, что code review можно прекращать прямо сейчас, иначе проблемы этот код начнет доставлять уже на следующей неделе. В общем случае – запихивание в классы внешних нерелевантных зависимостей есть большое зло.

Пятый признак – преднамеренное игнорирование или извращенное употребление паттернов программирования. Когда из-за страха перед великим и ужасным шаблоном “фабрика” образюутся иерархии, где один-единственный параметр конфигурации спускается каждым промежуточным классом вниз на десять уровней, чтобы там ему где-то делали if или switch..case, когда при программировании на C++ боятся использовать RAII или бьют по рукам за bind; или же, наоборот, пихают повсюду smart pointers (доморощенные, конечно же, бо boost – это слишком сложно), жди беды. Очень быстро кто-то посередине той иерархии забудет, зачем нужен этот мистический параметр, нигде в обозримых окрестностях не применяющийсяи “пофиксит” код, обеспечив остальных неделями отладки (ведь юнит-тесты для такого кода писать не принято – он же очень сложный!); неиспользование RAII рождает трудноуловимые дедлоки, а умные поинтеры дают вкусить циклических зависимостей по полной.

Шестой признак – использование наследования там, где нужна агрегация. Например, есть интерфейс классов, есть базовый класс, которые реализует 85% всей требуемой функциональности. Позднее выясняется, что функциональность реализована вообще на 100%, а единственное требуемое различие – это разное поведение в случае создания класса с определенными внешними параметрами. Например, в реальной жизни есть у нас автомобиль. Точнее, его кузов. Во всем мире все производители просто ставят разные двигатели на один и тот же кузов и обзывют это обидным словом “комплектация”. В мире программирования часто происходит закат разума за ум – вместо того, чтобы создать класс “автомобиль” со свойством “двигатель”, начинают городить иерархию “автомобиль А с двигателем Б”, автомобиль Б с двигателем А” и так далее, что вообще противоречит здравому смыслу. Еще бы, класс автомобиль оказывается наследником класса “двигатель”. Проблема тут в том, что разобраться во всем этом хитросплетении и потом поддерживать ее становится решительно невозможно. В то же время правильный вариант – создать нужный объект тпиа “двигатель” и передать его новому объекту типа “автомобиль” при инициализации- не только сэкономил бы пару тысяч строк кода, но и позволил бы унифицировать и протестировать все имеющиеся автомобили и двигатели отдельно друг от друга и даже попробовать впоследствии легко примерить движок от Мерседеса на Запорожец.

Кто такой senior developer?

Сегодня пришли к выводу, что

Senior developer – это обычный программер, научившийся успешно спихивать на других часть своей работы.

Тогда Team Leader – это  Senior Developer, достигший в этом деле совершенства.

Все очень просто, оказывается.

Юнит-тестирование. Всем чтить!

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

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

ППКС.

Код: до и после н.э. Зачем на самом деле нужны юнит-тесты.

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

БОльшая половина кода нашего проекта на первый взгляд выглядит очень хардкорно. Если опустить пару моментов, где я вдоволь оттянулся с белой и черной шаблонной магией, то бросится в глаза, что остальная часть этой половины идет вразрез с заветами секты “ООП”  и ее течения “многоэтажные иерархии классов” (да, я редко сажаю леса вроде приведенного на картинке ниже). В глаза бросится, да не долетит, а упадет и стечет (может быть даже прямо в ботинки).

Многоэтажная иерархия
При ближайшем рассмотрении

…копи-паста и святого чекина. Энтер.

Очень сегодня удивился на работе. Очень-очень.

Оказывается, вполне неглупые вроде люди до сих пор до глубины души уверены, что код, который был только что, буквально 10 секунд назад, checked in в систему управления кодом, окончателен и дальнейшему изменению не подлежит.

Узнал я об этом совершенно случайно, в момент, когда во время code review (на который по разным причинам уже накопилось слишком много кода) мне отметили, что этот код incomplete. Потому что не все запланированные фичи реализованы. С чем я категорически согласился, отметив, что завершить все фичи мы сможем не раньше, чем реализуем еще вот ту, ту и ту фигню, которую мы можем начать писать только после того, что приведем текущий кусок кода в более-менее живой вид. Ответ мне был таков, что моя вера в светлое, разумное и вечное забилось в какую-то темную дыру, закрыла за собой дверь, повесив на нее табличку “до пятницы не беспокоить” и заперлась на замок. “Мы должны реализовать эту фичу перед коммитом”, услышал я.

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

Когда же они наконец поймут, что в отличие от waterfall, который работает только на бумаге, agile работает и в реальном мире?

Ненавижу

Когда люди, еще толком не начав разработку, пихают в проект тонны самодельных велосипедов вроде “потокобезопасных строк” или векторов и прочего булшита. Которые имеют непонятно какой интерфес и только самому автору известно как работают.

При этом ни написано еще ни строчки функционала, ни одного интерфейса и ни одного теста.То есть совершенно непонятно, понадобится ли эта фигня вообще и если понадобится, то должна ли она работать именно так, как сейчас (а как я уже отметил, совершенно непонятно, как оно вообще работает), а уже начали кидаться высокопарными словами о “потокобезопасности” (кому оно нафиг надо если проект в итоге окажется однопоточным?), lock-free (ага, lock-free механизмам самое место на однопроцессорной машине), “энумераторах”, “словарях” и о том, что стандартная библиотека для нашей задачи совсем не подходит.

Короче, напоминает постройку забора методом Советской Армии – пишем слово “Хуй”, а потом к нему прибиваем гвозди.

РабочеЭ

Любимые на сегодня фразы

Мы не используем boost, потомушта программисты на техподдержке его не знают.

Мы не используем templates, потомушта программисты на техподдержке их не знают.

Продолжать можно до бескончености. Угадайте, на что в итоге похож код?

Утреннее с RSDN

Меняются поколения, вселенные встают и рушатся, а всё так же ругают язык, придуманный Страуструпом.

Ну и продолжают доставлять стандартным священным ужасом перед мифическим “ручным управлением памятью”

Как раз серверы, особенно на множестве процессоров лучше писать на более выскоуровныеых языках, иначе быстро замучаетесь с ручным управлением памятью и блокировками.

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

Deadlock for free или почему нельзя делать suspend потокам

Сегодня коллега напоролся на дамп программы, которая зависла. Такое бывает, но когда затык происходит на int* pInt = new int, Штирлицу положено насторожиться.

Налицо deadlock, но прикол в том, что в коде никакой синхронизации в принципе нет вообще. Зато была приаттачена куча всяких bounds-checker’ов вкупе с отладчиками, что заставило меня вспомнить, что я когда-то где-то читал о том, что  неосторожное обращение с менеджером памяти может привести к дедлоку.

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

Так вот, интернет уже неоднократно объяснил, что делать suspend потоку извне – очень, очень плохая идея. Есть миллиард объяснений, почему, но все они сводятся к общему знаменателю – поток сам распоряжается своими ресурсами и поэтому должен сам контроллировать свое поведение. Даже если на первый взгляд поток не использует никаких разделяемых ресурсов, при более пристальном рассмотрении это может оказаться совсем не так.

Любой поток делит как минимум один ресурс с остальными потоками. Оперативную память. Распределение памяти контроллирует менеджер памяти, который, как правило, общий для всех потоков, в определенных рамках, конечно. Соответственно, есть очень неплохие шансы, что при процедура выделения памяти синхронизирована внутри менеджера памяти, грубо говоря, менеджер имеет mutex внутри. А дальше все просто – Вася запустил поток, поток радостно поскакал по инструкциями и весело побежал за оперативкой. Менеджер памяти залочил было мютекс, а тут Вася бац! Suspend потоку! И тут же в другом потоке начинает ломиться к тому же самому менеджеру памяти, которому пофиг, ибо мютекс зажат другим потоком. И, заметьте, совершенно бесплатно приходит им всем, включая Васю, первый, второй поток и менеджер памяти злой дед лок.

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

C++ мантра или почему порядок вызова конструкторов так важен.

Читать вдумчиво, с придыханием, каждое утро по 128 раз

“Порядок вызова конструкторов членов класса определяется только порядком их появления в объявлении класса. Список инициализации порядок не определяет”!

Полдня убил сегодня на поиск бага. Был класс с членом-смартпоинтером и еще одним членом, который инициируется таким же смартпоинтером:

class MyClass{
public:
MyClass(SmartPointerToSomeThing);
private:
    SomeOtherClass m_OtherClass;
    SmartPointerToSomeThing m_VeryImportantSmartPointer;
};

MyClass::MyClass(SmartPointerToSomeThing Ptr):
    m_VeryImportantSmartPointer(Ptr),
    m_OtherClass(m_VeryImportantSmartPointer)	///< fuck up is here
{
}

// Has to be instead:
MyClass::MyClass(SmartPointerToSomeThing Ptr):
    m_VeryImportantSmartPointer(Ptr),
    m_OtherClass(Ptr)                                        ///< Note - Ptr used instead of member
{
}

Что произошло в первом случае?

Да ничего страшного, просто члену нашего класс m_OtherClass  подсовывался еще не инициализированный экземпляр умного указателя. А не инициализирован он был потому, что в интерфейсе класса самрт поинтер этот объявлен позже m_OtherClass.

Что интересно, найти такой баг может быть очень-очень-очень сложно.




7 visitors online now
7 guests, 0 members
Max visitors today: 7 at 12:19 am MST
This month: 20 at 03-19-2010 10:38 am MST
This year: 41 at 01-23-2010 03:43 am MST
All time: 41 at 01-23-2010 03:43 am MST