30 июня 2013 в 16:32

Умение видеть абстракции



Моему сыну, как и многим мальчишкам, нравятся автомобили. Причём чем они больше и необычнее — тем больше нравятся. Когда мы идём по улице, а мимо проезжает эвакуатор или снегоуборочная машина, он неизменно дёргает меня за руку, указывает на заинтересовавший его объект и говорит: «Папа, б-р-р!». Говорит он так потому, что ему один год и вышеуказанные два слова составляют 40% его словарного запаса. Тем ни менее, в общем мысль понятна — обратить внимание на автомобиль. Давайте подумаем, каким образом ребёнок в возрасте 8-10 лет сказал бы своему сверстнику то же самое. Что-то вроде «Ух ты, смотри какая крутая тачка!», да? Мысль та же, но обратите внимание — уже шесть слов вместо двух. И, наконец, представьте, каким образом то же самое скажет человек лет в тридцать: «Эй, смотри, да это же Ferrari California 2008-го года выпуска с двигателем V8 мощностью в 454 лошадиных силы и 7-ми скоростной коробкой-автоматом! Она до сотни разгоняется за 3.9 секунды!». Да, здесь уже больше деталей, но, если вы не автомеханик или фанат Ferrari — они вам скорее всего не нужны и не важны. Основная же мысль — всё та же, что и в «Ух ты, смотри какая крутая тачка!» или «Папа, б-р-р!». Но выражена она уже в 30 слов.

Вы заметили, как абстракция «интересный автомобиль» обросла деталями и нюансами, стала занимать существенно больше места в тексте и времени на понимание, анализ и ответ? То же самое происходит и с программным кодом.

О чём вообще идёт речь


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

Посмотрите на «умершие» проекты. Очень редко они погибают от того, что программист не смог на 10% повысить скорость работы, или потому что не смогли прикрутить нужную библиотеку. Чаще всего причина закрытия формулируется в духе «существующая архитектура делает принципиально невозможным дальнейшее развитие». Вот она, ошибка в видении абстракций. Кто-то когда-то давно не увидел, что несколько сущностей на самом деле являются одной, или что одна может иметь несколько представлений, или что на самом деле не клиент должен дёргать сервер, а наоборот, или что в протокол неплохо бы заложить возможность расширения — и вот он, грянувший спустя годы гром последствий.

Паттерны


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

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

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

Инструменты


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

Пример плохого кода


Несколько лет назад был пик популярности текстового редактора Notepad++. Десятки миллионов загрузок, приятный минималистичный интерфейс, плагины. Начало было очень хорошее, ничто не предвещало беды. За последние пару лет данный текстовый редактор сдулся и фактически застопорился в своём развитии. Вот график его загрузок.


В чём же причины? Я не берусь называть их все, но вот одна. Давайте посмотрим на один файл из его исходников.
NppBigSwitch.cpp

Внимательнее разберём некоторые части этого кода.

Непонимание понятия 'имя переменной'.
Macro m = _macro;

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

Непонимание работы систем контроля версий
/*
		case NPPM_ADDREBAR :
		{
			if (!lParam)
				return FALSE;
			_rebarTop.addBand((REBARBANDINFO*)lParam, false);
			return TRUE;
		}

		case NPPM_UPDATEREBAR :
		{
			if (!lParam || wParam < REBAR_BAR_EXTERNAL)
				return FALSE;
			_rebarTop.reNew((int)wParam, (REBARBANDINFO*)lParam);
			return TRUE;
		}

		case NPPM_REMOVEREBAR :
		{
			if (wParam < REBAR_BAR_EXTERNAL)
				return FALSE;
			_rebarTop.removeBand((int)wParam);
			return TRUE;
		}
*/

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

Непонимания того, где важнее скорость работы программы, а где - скорость работы программиста
LRESULT Notepad_plus::process(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
...
 // тут 1800 строк кода
...
}

Ничего так размерчик для одной функции, да? Автор, наверное, пытался сэкономить целый десяток наносекунд на вызов каждого отдельного кусочка кода через отдельную функцию. Молодец, сэкономил. И получил код, в котором противно копаться и нужно тратить кучу времени на понимание и исправление.

Непонимания абстракций 'флаг', 'константа', 'магическое число'.
nmdlg->Items[i] = 0xFFFFFFFF; // indicate file was closed
...
if ((lParam == 1) || (lParam == 2))
...
long view = MAIN_VIEW;
view <<= 30;
return view|i;

Что такое 0xFFFFFFFF, 1, 2 и 30? Ах да, 0xFFFFFFFF — означает что файл был закрыт. Какой чудесный комментарий, как всё наглядно! Было, видимо, очень быстро и удобно просто набросать цифры в код — компилируется ведь. Нет времени объяснять, поехали дальше, да?

Отсутствие абстракции над кодировками
				case COPYDATA_FILENAMESA :
				{
					char *fileNamesA = (char *)pCopyData->lpData;
					CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams();
#ifdef UNICODE
					WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance();
					const wchar_t *fileNamesW = wmc->char2wchar(fileNamesA, CP_ACP);
					loadCommandlineParams(fileNamesW, &cmdLineParams);
#else
					loadCommandlineParams(fileNamesA, &cmdLineParams);
#endif
					break;
				}

				case COPYDATA_FILENAMESW :
				{
					wchar_t *fileNamesW = (wchar_t *)pCopyData->lpData;
					CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams();
					
#ifdef UNICODE
					loadCommandlineParams(fileNamesW, &cmdLineParams);
#else
					WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance();
					const char *fileNamesA = wmc->wchar2char(fileNamesW, CP_ACP);
					loadCommandlineParams(fileNamesA, &cmdLineParams);
#endif
					break;
				}
			}

            return TRUE;
        }

В каждом месте, где имеет значение в юникоде скомпилирована программа или нет, мы видим конструкции, нагружающие мозг программиста ненужными в данный момент вещами. Даже в Win32 API, где многие функции имеют юникодную и неюникодную версии мы не думаем каждый раз что вызывать, просто пишем «MessageBox», а благодаря макросам это заменяется на MessageBoxA или MessageBoxW. Это позволяет подняться над данным уровнем, отключить мозг от необходимости помнить об этой детали. Авторам Notepad++ такой путь, видимо, кажется слишком лёгким.

Отсутствие слоя абстракции над UI-примитивами операционной системы
::MoveWindow(_rebarTop.getHSelf(), 0, 0, rc.right, _rebarTop.getHeight(), TRUE);
...
::SendMessage(_statusBar.getHSelf(), WM_SIZE, wParam, lParam);

Каждый раз, когда нам нужно передвинуть окно, спрятать его или выполнить какое-либо иное действие с элементом интерфейса — дёргаются напрямую функции Win32 API. Никаких библиотек интерфейса, никаких обёрток, классов — ничего. В итоге — куча лишнего и дублирующегося кода, абсолютная непортируемость на другие ОС, все недостатки Win32 API — прямо внутри нашего кода. Причём авторы выставляют такой подход как преимущество продукта, дескать, никаких лишних компонентов! Это просто ужас. Сотые доли процента прироста производительности (в лучшем случае) — и ад в исходниках.

Как результат всего вышеперечисленного, разработка Notepad++ является катастрофически сложным и медленным делом. Мой патч, исправляющий пару важных для меня багов висит в списке «на рассмотрении» уже полгода, вместе с почти 200 другими патчами. Автор, конечно, время от времени что-то из них принимает, но вы сами понимаете — делать это быстро с такой кодовой базой абсолютно невозможно. Мне очень жаль показавшийся мне когда-то хорошим редактор, но вы видите сами — смерть неизбежна.

Пример хорошего кода


Возможно, вам известна такая популярная библиотека как Qt — о ней в последнее время много пишут на Хабре. Я, опять-таки, не берусь утверждать, что знаю лучше всех все причины её успеха, но вот вам одна из них. Вся библиотека построена на прекрасном ядре абстракций: тут есть абстракции от платформы, от сети, от элементов интерфейса ОС, от кодировок, да практически от чего угодно. Взглянув на любой компонент Qt нам не нужно лезть сильно глубоко вниз или вверх, чтобы понять как он работает. Причём всё это не благодаря хорошей документации, а из-за кода самой библиотеки.

Давайте посмотрим на один из заголовочных файлов библиотеки Qt.

qpdfwriter.h
#ifndef QPDFWRITER_H
#define QPDFWRITER_H

#include <QtCore/qobject.h>
#include <QtGui/qpagedpaintdevice.h>

QT_BEGIN_NAMESPACE


class QIODevice;
class QPdfWriterPrivate;

class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice
{
    Q_OBJECT
public:
    explicit QPdfWriter(const QString &filename);
    explicit QPdfWriter(QIODevice *device);
    ~QPdfWriter();

    QString title() const;
    void setTitle(const QString &title);

    QString creator() const;
    void setCreator(const QString &creator);

    bool newPage();

    void setPageSize(PageSize size);
    void setPageSizeMM(const QSizeF &size);

    void setMargins(const Margins &m);

protected:
    QPaintEngine *paintEngine() const;
    int metric(PaintDeviceMetric id) const;

private:
    Q_DISABLE_COPY(QPdfWriter)
    Q_DECLARE_PRIVATE(QPdfWriter)
};

QT_END_NAMESPACE

#endif


Не пугайтесь возможно незнакомых вам макросов — не о них речь. Я хочу обратить ваше внимание на другую вещь. Заметьте, в этом классе нет приватных свойств. И почти ни в одном другом классе Qt — тоже нет. Вернее, на самом деле в каждом из них есть ровно по одной приватной переменной — это неявно объявленный через макрос Q_DECLARE_PRIVATE указатель на подкласс, в котором уже и находятся все свойства и часть приватных методов. Сделано это по официальному объяснению для обеспечения бинарной совместимости — получается что данный класс в любой версии Qt имеет один и тот же размер (поскольку место для хранения одного указателя константно в пределах платформы) и его можно спокойно передавать туда-сюда между модулями, не боясь каких-нибудь там segmentation fault.

На самом деле для меня вся прелесть этого решения в другом. Смотрите — мы открываем заголовочный файл и что же мы видим? Только публичные методы. Вы впервые видите этот заголовочный файл (да и вообще библиотеку Qt, может быть) — но вы ведь уже поняли, что это за класс и как его использовать, правда? Все внутренности изящно скрыты в приватном подклассе. К сожалению, классический С++ заставляет программиста смешивать в заголовочном файле и то, что нужно внешнему пользователю класса и его внутренности. Но в Qt при чтении заголовочного файла — мы видим чёткое послание от разработчиков Qt: «Внутренности этого класса тебе не нужны. Не твоё дело что там и как, это абстракция — используй её через публичные методы». И это прекрасно, чёрт возьми! Я хочу видеть все библиотеки в подобном стиле. Покажите мне нужные мне вещи сразу и спрячьте ненужные так, чтобы их нужно было искать долго и трудно. За этим подходом будущее, я уверен.

Выводы


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

Приятного программирования.
Автор: @tangro
Инфопульс Украина
рейтинг 158,89
Creating Value, Delivering Excellence

Комментарии (95)

  • +1
    Согласен с автором. Pimpl часто бывает нужен, а закомментированных кусков кода и т. п. быть не должно. Нужно уметь применять абстракцию. Хотелось бы добавить, что часто полезным будет внедрить систему автоматических текстов. Хорошо составленные тесты могут дополнять или даже заменять документацию публичной части класса.
    • +1
      Главное, что они заставляют абстрагироваться.
    • –2
      > автоматических текстов
      WAT
  • +2
    Почему же вы показываете внутренности Notepad и интерфейс Qt? Под капотом у последней тоже не все так замечательно.
    • +11
      Статья не претендует на полное сравнение кодовых баз проектов, я просто подметил интересные (плохие и хорошие) места в коде. И поверьте мне, аналогичный треш в Notepad++ ещё в сотнях мест, ровно как и отличные находки в Qt — тоже не.только в указанном моменте.
    • +4
      Там половина кода это doxygen дока, не так уж все и страшно. В Qt5 этот код еще и отрефакторили.
  • +3
    Спасибо, есть чему поучиться
  • +2
    Вы и вправду считаете, что снижение интереса к notepad++ вызван качеством исходного кода, а не удобством аналогов типа сублайма?
    • +17
      Это косвенное последствие. Notepad++ не успевает наращивать удобство с должной скоростью из-за ужасной кодовой базы. Спелчеккер прикручивали годами (вот только недавно закончили), карту кода передёрли с Сублима плохо и глючно и т.д. Я писал в статье о куче патч реквестов, висящих на рассмотрении по полгода просто потому, что интегрировать их слишком трудно и долго.
    • +7
      Или просто все скачали Notepad++, который занял свою целевую нишу (открытие всех непрофильных файлов) и который полностью устраивает и успокоились.
    • +12
      Я думаю, это связано прежде всего с тем, что его стали скачивать с официального сайта, а не с sourceforge (откуда брался график), и возможная тому причина указана в Википедии.
  • +17
    Я бы немного переформулировал основное послание: «Умейте видеть абстракции вовремя». Как старательно не проектируй, посреди реализации оказывается, что вот этот наследник использует только отрытый интерфейс родителя, а тот класс только какое-то время соответствовал этому интерфейсу, да и то только потому, что брал на себя не свои функции. К сожалению, многие абстракции кристаллизуются только после определенного времени разработки или даже использования системы. Угадать их заранее, по-моему, сроди ясновидению.
    Но это не значит, что не надо стараться :)
    • 0
      Вы сами признаете, что вовремя увидеть можно далеко не всегда. Предлагаю вариант «Не бойтесь рефакторинга». Ну и тут привет всем любителям сдельного фриланса, конечно :)
  • +5
    В Qt есть еще одна прекрасная вещь — документация. Только после двух лет работы с библиотекой мне однажды понадобилось посмотреть в код
  • +6
    Первый абзац какой-то странный. Обычно, чтобы объяснить что-то неочевидное, подбирают простой и понятный пример. Вы же наоборот: объясняете азбучные истины хорошего программирования на нетривиальном примере, который даёт чуть ли не больше поводов для размышления, чем предмет статьи. Что, впрочем, нисколько не умаляет её ценности.
  • +1
    Вы приводите пример смертных грехов программистов, код Notepad++ как иллюстрацию, получается слишком категоричные суждения в отношении автора Notepad++

    Приведённые примеры смахивают на технический долг, и никаких оснований предполагать, что автор кода — дегенерат, не дают, тем более нет доказательств что они коррелируют с количеством загрузок.

    Macro m = _macro;
    

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

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

    Ну зачем это писать? Все мы знаем что метод разросся не из-за попытки сэкономить наносекунды.

    Непонимания абстракций 'флаг', 'константа', 'магическое число'.

    Странно, в том же коде есть CURRENTWORD_MAXLENGTH MAX_PATH, значит автор понимал что такое константа? Просто маленький технический долг в этом месте.

    Отсутствие абстракции над кодировками

    Отсутствие слоя абстракции над UI-примитивами операционной системы

    Это, согласен, плохо.
  • +14
    А теперь сделаем шаг ещё выше по абстракциям и признаем, что главным является то, как называются компоненты (точнее, в каких терминах описывается их взаимодействие). Ошибка в этом месте тянет ложные абстракции и неудобные механизмы.

    Я говорю про уровень выработки терминологии.

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

    Мы можем сказать, что у нас есть мастер и слейвы. Можем сказать, что у нас есть сеть пиров с разным уровнем авторитетности (и задачей сети пиров является донесение данных до точек максимальной авторитетности), можем сказать, что у нас архивариус и генераторы.

    Как определим терминологию — так писать и будем. И это находится сильно выше, чем классы в приложении или конкретная модель пуш/поп/полл.

    Идеология и терминология определяют формат решения задач.
    • +2
      Модель предметной области?
      • +1
        До определённго предела — да. Предметная область даёт россыпь терминов, понятий и отношений, на которые удобно опираться. Но программа большую часть времени не занимается предметной областью, а занимается «чем-то своим» (я это очень не люблю — но такова реальность — большую часть кода, если считать строками, программы занимаются сами собой, а на работу выделяют лишь малую часть). Вот это «что-то своё» и требует своей терминологии и понятий, и оно же определяет насколько хорошо программа будет делать полезную часть себя.
    • –2
      Золотые слова. А если сделать шаг ещё выше, то окажется, что главная проблема — каша в голове. Человек просто или инженер, или нет. Или он понимает, как устроен мир, или нет. Или он настоящий инженер, или ремесленник от программирования.
      • 0
        Ох, я бы вот тут не делал таких резких заявлений. «Понимать как устроен мир» это очень круто. На каждое уверенное знание человека приходится дофига слоёв, которые знаешь плохо и неизвестное (но заведомо большое) количество слоёв, о которых не знаешь.

        Вот для меня было откровением существование SMM режима у процессоров. То есть маленькая революция в сознании того, как всё в компьютере работает. Хотя, вроде, системным уровнем интересуюсь и более-менее (думал что) знаю что делает процессор под управлением ОС.

        У любого программиста копнуть — будут дыры. Либо сверху, либо снизу.
        • +1
          Вы упомянули про терминологические проблемы (мастер-слейв vs. P2P vs. архивариус). А есть ещё целый класс проблем, когда разработчик придумывает сложные решения для простых задач, просто потому что не может отличить реальную проблему от выдуманной им самим. Например, программист борется с тормозами программы, управляющей станком, оптимизируя тяжёлые вычисления распознавания изображений с камеры (запускает несколько потоков, в каждом потоке обрабатывает часть изображения, потом синхронизирует их, собирая результаты), при том, что можно запустить распознавание одновременно с перемещением механических деталей станка, и внезапно окажется, что существующей производительности распознавания достаточно. Это не дыра в том понимании, как вы узнали про SMM. Это вообще неумение подумать чуть шире, понять суть проблемы, которую надо решить.

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

          Дыры есть у всех, это понятно. Проблема в том, на сколько порядков они отличаются в размерах. Вы узнали про любопытную фишку процессора, и у вас это знание легло на нужную полочку в «картине мира». Потому что у вас эта картина мира есть, и вы её пополняете по ходу развития. Дыры же в представлении о мире у большинства программистов настолько огромные, что даже мостик от исполнения JavaScript в браузере до аппаратуры компьютера они построить не в состоянии. Я уж не говорю про понимание работы стораджа или сети.
          • 0
            Стало интересно, а что вы имеете в виду под стораджем?
            • 0
              Те сложные штуки, которыми занимается камрад amarao.
    • 0
      Жаль, что не могу поставить больше одного плюса. Как я этого наелся, будучи тимлидом…
  • +26
    «Эй, смотри, да это же Ferrari California 2008-го года выпуска с двигателем V8 мощностью в 260 лошадиных сил и 7-ми скоростной коробкой-автоматом! Она до сотни разгоняется за 3.9 секунды!»

    «Товарищ Петров, неужели вы не видите, что мне, вашему давнему сослуживцу и верному другу, за шиворот капает расплавленный припой?»
    • +2
      Да, причем этот расплавленный припой — китайский, как и Ferrari California в данном примере.

      Автор попутал объем мотора этого автомобиля (260 cu in — 260 cubic inches) и его мощность в лошадиных силах (которая, как можно догадаться, у современной Ferrari никак не может быть 260 л.с.)
      • +3
        Автор признает, что в двигателях Ferrari действительно разбирается, как еноты в астрофизике.
        • +1
          Спасибо, автор. :-) Признание, достойное уважения.

          Но я б поправил всё же количество лошадей, для будущих поколений. 454.
          • +1
            Ок
  • +3
    Выводы не в тему, даже если я прекрасно вижу абстракции — ограничения в виде языка никуда не денутся, а абстракцию можно будет реализовать любым из «советов»: глобальной переменной, большими функциями, преждевременной оптимизацией и т.д.

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

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

    Вывод: идеальная абстракция должна быть (и может быть только!) на низких уровнях — как раз драйвера, ОС. При этом абсолютно неважно какой длины функция — она пишется «навсегда». А пользовательские интерфейсы, динамические страницы, отчеты и т.д. нужно реализовывать с худшей абстракцией, но с лучшим пониманием программистом. И тут уже нужны паттерны, советы от Гуру и т.д., отсутствие преждевременной (или вообще железо дешевле) оптимизации. Именно здесь успех Java и C#.
    Собственно, самые простые и понятные абстракции уже давно написаны (операционные системы, драйвера, основные фреймворки). И гораздо больше задач горизонтальных на высоком уровне абстракции — именно поэтому в почете советы по чистому отформатированному коду и горизонтальных связях.
  • +3
    Все что я пишу, это чистое ИМХО, но как правило, нужно это выделять, иначе не поймут.

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

    Ни одна из книжек по паттернам не говорит когда и какой использовать. И на мой скромный взгляд, не сможет, т.к. не существует методик по решению задачи написание программных продуктов.
    • +2
      Немного не так. Паттерны — это стандартный способ решить часто встречающуюся техническую задачу. В одних ситуациях стандартное решение будет хорошим, в других плохим, в третьих всё равно каким. Главное чтобы такая задача действительно стояла, а не пригрезилась разработчику :)
      • +1
        Что вы подразумеваете под технической задачей?
        Не может перед человеком стоять задачи создание существование объекта в единственном экземпляре, задача в любом случае звучит по другому.
        И опять же, стандартное решение какой то задачи не может быть плохим. Оно может не подходить(плохо подходить) к данной проблеме, но плохим быть не может
        • 0
          Задача, выраженная в терминах языка — переменные, классы, объекты, ссылки, интерфейсы и т.п., без всякой привязки к семантике общей задачи. Такие задачи возникают в процессе декомпозиции.
          • +1
            На самом деле наши определения паттерна не являются противоречивыми.
            Только мы с вами пытаемся по разному выделить главную функцию паттерна.
            Вы, так как сейчас пытаются преподносить паттерны, а я так как по сути они используются среди программистов.

            p.s.
            опять же уточняю, что тут всё моё субъективное ИМХО, т.к. я не видел ни 1 человека, который бы мыслил паттернами. Паттерны вторичны, они появляются не в голове, а при попытке перенести свою мысль в речь(или на письмо)
            • 0
              Как мне кажется, во фразе «я использовал синглтон» содержится и описание проблемы, и выбранное решение, а, если я вас правильно понял, по-вашему, только решение.
              • 0
                С интересной стороны вы это рассмотрели. Согласен, на мой взгляд тут мы видим только решение.
              • 0
                Мне кажется, что такие фразы все-таки опасны. Тут на, мой взгляд, и о проблеме и о конкретном решении (даже синглтон тот же можно слегка по-разному сделать, не говоря о трудностях перевода и разного background-а) приходится догадываться и есть риск ошибиться. У меня лично как-то обычно не было нужды в паттернах, как «промежуточном» слое терминологии между идеей и кодом. Быстрее и нагляднее получалось примеры кода накидать в 1-2 строки и сравнить подходы.

                Один мой бывший коллега так вообще только паттернами и разговаривал (часто, похоже, и мыслил тоже) и в переписке при просьбе руководителя расшифровать какой-то из не самых распространенных в С++ паттернов, дал ссылку на книгу на амазоне (не на википедию или еще какой-нить доступный текст). Причем, похоже, абсолютно без задней мысли.
                • 0
                  У меня тоже не было, пока не прочитал GoF и PoEAA и не понял, что многие свои решения можно объяснить одним-двумя словами.

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

                    Проблема в том, что общаться приходилось с разными людьми, а многие, как и я, забыли названия, или предпочитали читать другие книги по программированию. Да и обсуждать код с точностью до проектирования классов приходилось только со студентами, а там обычно уже не до паттернов, там бы просто «хорошие привычки» привить бы сначала. К моменту того, как они понимают архитектуру проекта и приобретают эти «хорошие привычки», они тоже многие паттерны уже в коде успевают по 100 раз увидеть.

                    Часто надо было обсуждать код с точностью до символа: когда мы сочинали либы и хотелось, чтобы лишнего мусора программистам писать не приходилось. Но тут уже не паттерны важнее были, а удобство, лаконичность и интуитивность.

                    (Если что, то я в основном на C/C++ писал, немного JS. Для Java тема, наверное, может быть чуть-чуть более актуальной)
            • –1
              В основном «паттерны» крутятся вокруг одного: использовать объект класса-наследника (предполагается что синглетон или что-то типа того) как эмуляцию объекта типа «функция» и потом применить стандартный приём из книжки пр функциональному программированию. Т.е. эмулировать функциональное программирование с помощью объектно-ориентированного (оно для этого подходит практически идеально). Дальше, если оверхед получился слишком большой, при возможности, как-то упростить. Т.е. всё вертится вокруг паттерна «Стратегия» (который встроен во многие современные ООП-языки в виде понятия «интерфейс»), который, собственно, и призван изображать из себя место для «первоклассного объекта типа 'функция'».

              Про паттерны у меня в голове всегда вертится вопрос: почему именно «паттерны», а не «библиотечные функции», что было бы, по идее, проще? А потому, что кусок кода нельзя просто так взять и впихнуть в вызов библиотечной функции в качестве параметра.
              • +1
                Во всех этих ваших шарпах спокойно существуют все эти функции первого класса. Так что если бы это было возможно, это было бы реализовано. Умение фукнциональщиков видеть функциональщину везде неудивительно, равно как и умение ООПщиков видеть одни только объекты.
                • 0
                  1) Я не «шарпист», а в Java это только планируется только частично в 1.8. В связи с невключённостью в мир .NET не знаю, насколько тема «паттернов» педалируется в нём.
                  2)
                  Так что если бы это было возможно, это было бы реализовано.
                  Есть немалая инерция. Насколько я знаю, «паттерны» (1994) старше чем Java (1995-1996) и старше чем С# (2000-2001) и вообще изначально относились только к C++, т.к. книга написана про C++. Кроме того, там скорее нужны не «функции» с заголовком и прочим оверхедом, а «блоки кода» как первоклассные объекты (такое к 1994 году уже было, например, в Clipper).
                  3) «Критика» «паттернов» на тему, что в функциональных языках они вырождаются в тривиальные вещи, опубликована ещё в 1996 — norvig.com/design-patterns/ppframe.htm
                  4) Вы так пишете, как будто ФП и ООП — это что-то плохое;-)
                  5) «Паттерны» — это сегодня исключительно про ООП (пример из Норвига из тех времён, когда вызов подпрограммы тоже был «паттерном», не в счёт). Потому, что ООП позволяет делать их больше, проще и лучше;-)
                  • 0
                    Я помню, у меня был несложный, но монструозный проект, построенный по всем паттернам ООП (ага, пек хлеб), в функциональном языке он получался намного короче. Но когда мне попозже понадобилось сделать что-то более приближенное к практике, то иммутабельность переменных и пр. заставляли меня писать такие костыли, что все эти фортраны с отутствием поддержки IEEE754 и записями а-ля «X=(X-X)+X» нервно курят в сторонке. А вот на шарпе с анонимками это было легко и приятно.

                    Я сам с математикой на «ты» и функциональные языки нравятся мне своей элегантнностю и лаконичностью, но в терминах ООП мне мыслить все же проще. А тот же шарп имеет практически те же функциональные возможности, что и какой-нибудь хаскель. Даже монады прикрутили :)
    • +1
      >Ни одна из книжек по паттернам не говорит когда и какой использовать. И на мой скромный взгляд, не сможет, т.к. не существует методик по решению задачи написание программных продуктов.
      Вообще-то большая часть канонической "… Паттерны проектирования" банды четырех состоит из обоснования когда, зачем и почему может понадобиться данный прием, а когда и почему не стоит им пользоваться.
  • +11
    Пижама сладкий прыгать Луна вопреки быстро люк?

    Что же тут непонятного? Когда от пижамы идёт сладковатый запах, даже на Луне, вопреки пониженной гравитации, быстро будешь прыгать ты, Люк. Более того, тут даже прямо само предложение содержит объяснение своих грамматических странностей. :)
    • +2
      У меня другие ассоциации. Люк настоящий, от марсохода.
    • –1
      > Пижама сладкий прыгать Луна вопреки быстро люк?

      translate.google.com —>
      русский —> китайский — >… (двадцать языков спустя)… —> болгарский —> английский

      «Despite rapid pajamas and cute way to jump?»

      Кажется, я понял, что автор пытался сказать…

    • 0
      «Пижама сладкий вопреки, люк быстро прыгать Луна»… Сурово…
  • +1
    Всё так, но ваше желание скрыть внутренности класса от программиста — чудовищно. Положите поля и методы в private, уберите private-секцию в конец файла, сгенерируйте документацию по public-части и расслабьтесь. Программист, который будет искать, почему абстракция работает не так, как ему кажется логичным, проклянет вас, если ему придется опускаться всё глубже и глубже в ад код. А те, кого всё устраивает, и так дальше документации не полезут.
    • +2
      И потом получить сложности при изменении этого private добра, пусть лежит в другом файле лучше.
    • +4
      Pimpl — точно такой же паттерн, как и все остальные. Он не хороший и не плохой. Просто нужно принять во внимание, зачем это нужно было в Qt и почему это не нужно вам, или boost.

      Qt — это большая библиотека, которая поставляется в основном в бианрном виде. И ее использует куча проприетарного софта, который просто так не пересоберешь. Поэтому им действительно нужно было обеспечить бинарную совместимость между версиями. В добавок, pimpl отлично совмещается с их COW. Но зачем такое может понадобиться не библиотеке — не понятно.
      • 0
        Скажем, что в случае плюсов COW часто бонусом дает PIMPL.
  • +2
    Вот график его загрузок.


    Ну это означает, что все скачали его, кому надо :)
    • 0
      А значит больше уже не надо, а почему?
      • +7
        Он скачан
  • +4
    Успех какого-нибудь популярного сегодня продукта (подставьте сюда свою любимую ОС, браузер, игру — да что угодно) определён именно тем, насколько хорошо спроектирована его архитектура, насколько хорошо высокоуровневые части отделены от низкоуровневых и друг от друга.


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

    Продуманная архитектура — это долгосрочный вклад в потенциал удержать успех, если таковой настанет. И снова-таки, не ключевой.
  • –1
    Спасибо, хорошая статья, но хотел обратить на один момент
    Ничего так размерчик для одной функции, да? Автор, наверное, пытался сэкономить целый десяток наносекунд на вызов каждого отдельного кусочка кода через отдельную функцию. Молодец, сэкономил. И получил код, в котором противно копаться и нужно тратить кучу времени на понимание и исправление.

    Почему бы не использовать тогда просто не использовать inline функции?
    И немного сэкономить время, и читаемо.
    • 0
      минусующие, объясните и мне
      • 0
        Я не минусовал, но могу предположить, что про «пытался сэкономить» это был сарказм для усиления эффекта, а не попытка понять автора. Суть — в последнем предложении.
  • +1
    Так какой же редактор использовать вместо Notepad++?! Скажите скорее, а то я завтра на работе не смогу толком работать…
    • +2
      Холиварчик желаете? Сейчас сюда набегут любители всего — от вима до ворда.
      • +1
        Ну насчёт ворда — это, конечно, совсем уж жестоко как-то…
        Я, например, пользуюсь стандартным редактором, встроенным в Far Manager 3.
        • 0
          >Ну насчёт ворда — это, конечно, совсем уж жестоко как-то
          Вам «бип-бип» от vim'а
  • 0
    Отличная статья! Подобраны, довольно часто встречающиеся, и жизненые примеры «плохого кода». Сам давно стараюсь пользоваться принципами «программирования от абстракции», а не «от реализации».
  • 0
    Кстати, очень развеселая подсветка синтаксиса. Хотел заставить подсвечивать его файлы определенного расширения (например, файлы *.config как XML). Пробовал в Опции -> Определение стилей (и кто же их туда запихнул), сработало. После этого пытаюсь добавить еще одно расширение, уже не работает (причем и config тоже). В общем, ужас один. А он мне очень нравился…
  • +3
    Зато, я знаю проекты которые загнулись на стадии проектирования, хотя могли бы развиваться (со временем рефакториться) если бы не усиленное желание некоторых программистов потратить кучу времени на архитектуру, вместо того, чтобы тупа сесть и написать Нотепад++. Кстати последним пользуемся всей фирмой до сих пор. Скорость и стабильность его работы одна из причин.
  • +3
    Я пойду против системы, но огромный свитч-кейс — это не самое страшное зло, особенно учитывая, что все ветки помечены константами либо известными из WINAPI, либо собственными, но тоже более-менее понятными. Я бы, конечно, всю ветку WM_COMMAND вынес в отдельный метод, а окромя этого с остальным вполне можно жить. По сути каждая ветка кейс — вещь в себе, как отдельная функция, и может исправляться независимо.
    • 0
      О, не подумайте что это один-единственный метод, который вышел большим из-за свича. Там в других файлах методы в сотни строк без всяких свичей.
    • 0
      Я бы каждую ветку вынес в отдельный метод. Тогда вся конструкция switch имела бы единственное назначение/ответственность — выбор нужного метода.
      • 0
        И изобретаем велосипед… фабрику заново :)
        • +1
          Это скорее стратегия.
    • +1
      Мое наблюдение: терпимость к такому switch-еванию еще очень сильно зависит от IDE/редактора. Но мешанина из табов и пробелов в 6-м(!) уровне вложенности должна намекать на то, что и автору кода написание все-таки дается не совсем легко.

      Но имхо, опенсорсный код должен все-таки более-менее нейтральный код-стайл выбирать, если он, конечно, не для галочки опенсорсный. Лично у меня проблема была бы в чтении не столько в количестве кейсов, а в том, что там сильно разнородный код: там и макросы пишут, и пути склеивают и чисто UI-код. Собственно, там помимо вышеописанных еще как минимум абстракций бизнес-логики (макрос, открытый документ, типа того) не хватает, чтобы этот switch их только «склеивал воедино»
      • 0
        В этом большом свитче нет ддаже банального единообразия поведения кейс-ветвей. Вернее не поведения, единого контракта в плане даже илинообразного выхода. Где return TRUE, а где $result = ...; break;
  • 0
    Сначала не понял, как у автомобиля может быть двигатель V8. Потом понял.
    • +3
      Назвать javasript интерпретатор v8 engine было офигеннейшей идеей для американского рынка. V8 — это мощность, скорость. Классика американского моторостроения, живая легенда, сердце любого маслкара, ну и заодно болидов наскара, драг-рейсовых и вообще почти всего, что движется на колёсах и похоже на машину.
      • +1
        Я, как далекий от авто тематики, долго врубался в название. Даже была версия, что это закодированное слово «weight»
  • +1
    Абстракции — это очень хорошо и правильно, и мне обычно больше нравится код с правильными абстракциями и написанный по SOLID-принципам, и сам я стараюсь писать именно такой.

    Но зачастую всё это не определяет успех проекта, даже опен-сорсного. Привожу пример из систем контроля версий. Для начала возьмём Bazaar, дела у которого, судя по всему, не очень:
    Bazaar had a lot going for it. It is written in a popular and accessible programming language. The code is clean, with lots of comments and good test coverage…

    We lost sight of what mattered for our users, focusing on features that were nice but perhaps not as necessary as we thought. We overengineered. We didn't get rid of the crufty unnecessary features. It's harder to comprehend, contribute to or fix performance issues in a large layered codebase. And the larger a codebase becomes, the larger the surface for bugs, the harder it is to refactor.
    (с) Bazaar Retrospective


    А вот известное мнение Линуса по поводу c++, абстракций и «объектных моделей» (которое вдохновляет и заставляет призадуматься, но до конца с ним согласиться сложно):
    If you want a VCS that is written in C++, go play with Monotone. Really. They use a «real database». They use «nice object-oriented libraries». They use «nice C++ abstractions». And quite frankly, as a result of all these design decisions that sound so appealing to some CS people, the end result is a horrible and unmaintainable mess.


    Всё же мой любимый принцип — KISS. :)
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Ок, данный конкретный факт и вправду вышел лажей. Моя вина. Это однако не отменяет ни примеров плохого кода, ни того факта, что патчи годами висят на рассмотрении, ни того, что по скорости наращивания функционала Notepad++ давно уступил как минимум Сублиму, ни общей идеи статьи о важности абстракций.

      Прочитать 20 фактов, придраться к одному слабому и на этой основе перечеркнуть все выводы — приём чёрной риторики, давайте не будем её применять.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Не думаю, что это хороший тест. Просто отобразить текст или отобразить его с подсветкой синтаксиса — это уже большая разница по времени. Тогда уж лучше сравнивать скорость работы с таким файлом — поиск в нём, переходы к строкам.
          П.с.: не пользовался ни Notepad++, ни Sublime, поэтому комментарий чисто теоретический
          • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Спасибо, душевно по Notepad++ прошлись. Со своей стороны могу добавить:

    «Молодец, сэкономил. И получил код, в котором противно копаться и нужно тратить кучу времени на понимание и исправление. „

    Тут можно выделить более конкретную проблему, чем абстрактные “противно» и «куча времени». Проблема — кошелек Миллера. Функцию такого размера физически не получится прочесть ни у одного программиста по вполне себе физиологическим соображениям. Поэтому как только там появится что-то кроме мега-свича (а оно появится, поверьте старику) — эта функция станет генератором фееричных, трудно ремонтируемых багов.
  • 0
    Интересное мнение.
    А думаете стоит вообще видеть эти абстракции и выносить все в модули на первом же этапе разработки? Например если разобрать абстракцию автомобиля до скелета, то, думаю, для многих, останется двигатель и шасси. А можно прикрутить спереди пропеллер и видоизменить кузов, и вот получается самолет. Или прикрепить снизу винт — получается лодка. Но это так себе самолет и фиговенькая лодка. Чтобы получить летающий аппарат помощнее нужно очень сильно отрефакторить и двигатель и шасси.
  • +1
    Только я где-то написал, что в текстовые исходники надо подмешивать образы (хотябы графические, хотябы стрелочки), так меня сразу отпинали. Зашел сюда — здесь болтают про текстовые абстракции и радуются… Поймите же: можно сколько угодно что угодно говорить, но пока программы пишутся буквами — мы все будем их писать со скоростью муравья или черепахи, и никакие абстракции не помогут, если нельзя их визуально задокументировать.
    • 0
      Scratch? Ну так что-то он не шагает по планете победоносной поступью сламливая на своем пути С/С++, делфи, сисиетку и прочих монстров продакшена.
      • 0
        Этот скратч не компилируется в бинарники. Только сам скратч умеет его исполнять. К тому же его возможности очень ограничены.
        И в этом причина.
      • 0
        Я посмотрел скрин в википедии, это игрушка для детей, которые почти не умеют программировать. Какой еще на нём продакшен делать…
        Я, наверно, напишу статью с концепцией многомерного программирования на какой-нибудь хабр в своё время, но вижу, что обосрут, заминусуют и сгнобят меня намертво, если это сейчас сделать. Я как-то ковырялся в моделировании, если кто-то слышал GPSS и Arena — делают одно и тоже. Но в первом случае текстовые проги писать надо, которые никто не понимает, а во втором просто рисуешь схемку, задаешь параметры и готово. Это как усилитель на транзисторах можно схематично начертить, а в первом случае (по принципу GPSS) это была бы текстовая программа, как исходник файла векторной графики:)
  • +1
    Абсолютно согласен с автором. Только тут есть и другая крайность.
    Часто сталкиваюсь с кодом авторства миддлов-жуниоров, в котором они начинают накатывать абстракции на всё подряд, даже там где они не нужны, и нужно сделать просто уникальную реализацию какого-то микромодуля, и эта реализация уложится в 30 строк кода.

    Даже если потом этот модуль придется абстрагировать, То рефакторить 30 строк до более абстрактной сущности — проблем не составит, а вот продуманная сложная абстрактная архитектура может так и не использоваться ни разу до завершения проекта. А время уже потрачено, и вместо 30 строк хардкода, получаем в проекте 5 классов в разных пакетах, которые хренпойми как взаимодействуют.

    Я поддерживаю схему разработки:
    Говнокод, выполняющий поставленную задачу -> рефактор и абстракция с архитектурой поверху -> релиз.

    Часто рефактор и не требуется, а часто он требуется через полгода, когда реализованная сущность уже доросла до самодостаточного модуля, требующего неких оберток и абстрктного представления.

    Хардкод тоже плохо, и если вещи требующие абстракции не были замечены изначально — потом исправить так же сложно. Но нужно таки с умом подходить. И хардкод и абстракции хороши каждый для своих задач.
    • 0
      Хорошо помогает выявить необходимость абстракции написание тестов до говнокода, выполняющего задачу.
      • 0
        Пока никак не перейду на ТДД… Планирую в следующем проекте попробовать. Но думаю что так и есть. По логике ТДД — тесты являются представлением ТЗ в виде кода, если я правильно понимаю. И такая идеалогия мне оч нравится, я люблю абстрагировать свой код до логических бизнес-сущностей, и стараюсь чтобы сущности в коде соответствовали описанию задачи словами.
        • 0
          Правильно понимаете, но даже до такого уровня абстракций необязательно подниматься (или опускаться?). Зачастую для построения хорошей абстракции достаточно понимания «что-то тест (прежде всего инициализирующая часть) слишком сложный становится». Утрируя — что за абстракция, если у неё 7 конкретных зависимостей, причем явно не задаваемых, а только через рефлексию?
          • 0
            Понял, спасибо)
            Только кармы не хватит даже плюсик на месадж паставить) Так что просто спасибо)

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка