Брутально и бессердечно о программировании и проектировании
ГлавнаяФорумПаттерныАнтипаттерныТест-драйвЗаметкиВопрос-ответКнигорецензииСправочная

45. Хороший технарь ≠ хороший программист

Говнокод онлайн — первый русскоязычный Интернет-проект о говнокоде
 
Хороший технический специалист практически никогда не бывает хорошим программистом. Живым подтверждением тому является бесчисленное множество ужасающих библиотек, сложных в реализации с технической точки зрения. В качестве примера таких библиотек можно рассмотреть математические библиотеки, библиотеки для кодирования и обработки изображений, кодирование потокового видео и аудио, криптографические библиотеки, и так далее. К сожалению, большинство технических специалистов (включая самых матерых экспертов) живут с иллюзией того, что для того, чтобы написать хорошую техническую библиотеку, достаточно быть хорошим техническим специалистом. К сожалению, этого недостаточно.
Технический специалист сосредотачивает свои усилия на вопросах, непосредственно связанных с результатом работы библиотеки, а именно — на производительности и, если это имеет место, качестве выходных данных. При этом качество кода и вообще культура программирования нещадно приносятся в жертву результату, подобно тому, как крестоносцы вырезали половину Константинополя под предлогом «освобождения Гроба Господня».
Новички в программировании ошибочно полагают, что они еще не доросли до некоторого SDK, если им не удалось понять, как этим SDK пользоваться. Новички, не расстраивайтесь — в действительности причина здесь ровно обратная — авторы SDK еще не доросли до того, чтобы писать SDK.
Современные технические библиотеки — это грустно и печально. Обойдите все технические SDK, предоставляемые самыми крупными компаниями и организациями, от GNU до Microsoft, от NVIDIA до Intel, от Daz 3D до Autodesk — нигде, нигде вы не найдете ни одного SDK, ни одной библиотеки, ни одного модуля, хотя бы отдаленно напоминающего по качеству тот же Boost. Все что вы найдете — это брутальный говнокод, представляющий собой кровавый замес из процедурного C, недообъектного и недотипизированного C++, полнейшего непонимания паттернов проектирования, полного отсутствия вообще какой-либо культуры программирования и хотя бы элементарной заботы о собственном пользователе.
 
Intel
Компания Intel очень гордится своей библиотекой «Integrated Performance Primitives». Библиотека предоставляет функциональность по кодированию видео, обработке изображений, трехмерному рендерингу, и так далее. То есть популярные задачи, требующие хороших вычислительных мощностей. Казалось бы, кому как не Intel лучше всех справиться с этой задачей?
Зайдя в папку «samples», мы увидим, что Intel любезно предоставила аж пять примеров работы с форматом JPEG/JPEG2000. Однако, наша радость становится полной, когда мы обнаружим, что размеры исходников примеров варьируются от 850-ти килобайт до 2-х мегабайт. Для сравнения — библиотека Boost.GIL, решающая гораздо более широкий круг задач, снабжена примерами размером в несколько сот байт, коротко и по сути объясняющими суть предоставляемой функциональности.
 
Пример кричащего говнокода из библиотеки «Intel IPP», свидетельствующий о полнейшем дилетантстве технаря как программиста:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
template<class T,int N> struct IMG {
private:
    T *img_mem; // ptr to allocated memory
protected:
    T *img;     // holds current ROI start position (left-top point)
public:
    int wstep;               // image pitch, set in constructor
    IPSize imgSize;          // memory image size, set in constructor
    IPSize roiSize;          // ROI, initially cover the whole image,
    IppStatus lastStatus;    // status of the last operation, where the image is a destination

    IMG(int width, int height) :
        imgSize(width,height),
        roiSize(width,height),
        lastStatus(ippStsNoOperation)
    {
        wstep = imgSize.width * N * sizeof(T);
        img = img_mem = (T*)ippiMalloc_8u_C1(wstep,imgSize.height,&wstep); // malloc in bytes
    }

    // moves left-top point of the image, modifies roiSize correspondently
    virtual void border(int wpixels,int hpixels)
    {
        img = (T*)((Ipp8u*)img+wstep*hpixels+wpixels*(N*sizeof(T)));
        roiSize.resize(-wpixels,-hpixels);
    }
    // modifies current roi rectangle - shifts left, top, right, bottom borders
    void roiRect(int addl, int addt, int addr, int addb)
    {
        border(addl, addt);
        roiSize.resize(addr, addb);
    }
    //restores ROI to the whole image
    virtual void restoreRect()
    {
        img = img_mem;
        roiSize = imgSize;
    }

    virtual ~IMG() { ippiFree(img_mem); }

    // returns current ROI start position (left-top point)
    inline T *ptr() const { return img; }
};
 
LEADTOOLS
Компания LEADTOOLS со своего сайта заявляет, что она — «The World Leader in Imaging Development SDKs». Действительно, чем только они не занимаются — даже обслуживают такую серьезную отрасль как медицина. На деле же качество кода в SDK от LEADTOOLS точно также бросает в дрожь — точно такой же говнокод, такие же примеры использования в сотни килобайт, такое же отсутствие вообще какого-либо понимания о культуре программирования.
Все примеры использования библиотеки LEADTOOLS представляют собой MFC-приложения, в которых 99,9% кода — это код MFC и 0,1% кода — это код самих LEADTOOLS (то есть именно тот код, ради которого пользователь и полез в пример). Без хороших познаний в области архитектуры построения MFC-проектов (кстати та еще трава), пользователь лишен всяких шансов научиться работать с изображением из примеров LEADTOOLS.
 
Одни только заголовочные файлы LEADTOOLS не оставят равнодушным даже самого видавшего виды программиста:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//
// Вырезано 1500 аналогичных строк до
//

extern LWRP_EXPORT pL_GETLOADRESOLUTION   pL_GetLoadResolution;
extern LWRP_EXPORT pL_GETFILECOMMENTSIZE  pL_GetFileCommentSize;
extern LWRP_EXPORT pL_GETTAG  pL_GetTag;
extern LWRP_EXPORT pL_LOADBITMAP pL_LoadBitmap;
extern LWRP_EXPORT pL_LOADBITMAPLIST   pL_LoadBitmapList;
extern LWRP_EXPORT pL_LOADBITMAPMEMORY pL_LoadBitmapMemory;
extern LWRP_EXPORT pL_LOADFILE   pL_LoadFile;
extern LWRP_EXPORT pL_LOADFILETILE  pL_LoadFileTile;
extern LWRP_EXPORT pL_LOADMEMORYTILE   pL_LoadMemoryTile;
extern LWRP_EXPORT pL_LOADFILEOFFSET   pL_LoadFileOffset;
extern LWRP_EXPORT pL_LOADMEMORY pL_LoadMemory;
extern LWRP_EXPORT pL_READFILECOMMENT  pL_ReadFileComment;
extern LWRP_EXPORT pL_READFILECOMMENTEXT  pL_ReadFileCommentExt;
extern LWRP_EXPORT pL_READFILECOMMENTMEMORY  pL_ReadFileCommentMemory;
extern LWRP_EXPORT pL_READFILETAG   pL_ReadFileTag;
extern LWRP_EXPORT pL_READFILETAGMEMORY   pL_ReadFileTagMemory;
extern LWRP_EXPORT pL_READFILESTAMP pL_ReadFileStamp;
extern LWRP_EXPORT pL_SAVEBITMAP pL_SaveBitmap;
extern LWRP_EXPORT pL_SAVEBITMAPBUFFER pL_SaveBitmapBuffer;
extern LWRP_EXPORT pL_SAVEBITMAPLIST   pL_SaveBitmapList;
extern LWRP_EXPORT pL_SAVEBITMAPMEMORY pL_SaveBitmapMemory;
extern LWRP_EXPORT pL_SAVEFILE   pL_SaveFile;
extern LWRP_EXPORT pL_SAVEFILEBUFFER   pL_SaveFileBuffer;
extern LWRP_EXPORT pL_SAVEFILEMEMORY   pL_SaveFileMemory;
extern LWRP_EXPORT pL_SAVEFILETILE  pL_SaveFileTile;
extern LWRP_EXPORT pL_SAVEFILEOFFSET   pL_SaveFileOffset;
extern LWRP_EXPORT pL_SETCOMMENT pL_SetComment;
extern LWRP_EXPORT pL_SETLOADINFOCALLBACK pL_SetLoadInfoCallback;
extern LWRP_EXPORT pL_GETLOADINFOCALLBACKDATA   pL_GetLoadInfoCallbackData;
extern LWRP_EXPORT pL_SETLOADRESOLUTION   pL_SetLoadResolution;
extern LWRP_EXPORT pL_SETTAG  pL_SetTag;
extern LWRP_EXPORT pL_STARTCOMPRESSBUFFER pL_StartCompressBuffer;
extern LWRP_EXPORT pL_STARTFEEDLOAD pL_StartFeedLoad;
extern LWRP_EXPORT pL_STOPFEEDLOAD  pL_StopFeedLoad;
extern LWRP_EXPORT pL_WRITEFILECOMMENTEXT pL_WriteFileCommentExt;
extern LWRP_EXPORT pL_WRITEFILESTAMP   pL_WriteFileStamp;
extern LWRP_EXPORT pL_SETSAVERESOLUTION   pL_SetSaveResolution;
extern LWRP_EXPORT pL_GETSAVERESOLUTION   pL_GetSaveResolution;
extern LWRP_EXPORT pL_GETDEFAULTLOADFILEOPTION  pL_GetDefaultLoadFileOption;
extern LWRP_EXPORT pL_GETDEFAULTSAVEFILEOPTION  pL_GetDefaultSaveFileOption;
extern LWRP_EXPORT pL_WRITEFILETAG  pL_WriteFileTag;
extern LWRP_EXPORT pL_WRITEFILECOMMENT pL_WriteFileComment;
extern LWRP_EXPORT pL_CREATETHUMBNAILFROMFILE   pL_CreateThumbnailFromFile;
extern LWRP_EXPORT pL_GETJ2KOPTIONS pL_GetJ2KOptions;
extern LWRP_EXPORT pL_GETDEFAULTJ2KOPTIONS   pL_GetDefaultJ2KOptions;
extern LWRP_EXPORT pL_SETJ2KOPTIONS pL_SetJ2KOptions;

//
// Вырезано 1500 аналогичных строк после
//
 
NVIDIA
Библиотека PhysX от NVIDIA является еще одним живым подтверждением основного тезиса данной статьи. Конечно, непосредственно NVIDIA тут не при чем — PhysX был куплен компанией NVIDIA у компании AGEIA вместе с самой компанией AGEIA, и существует чуть более чем стопроцентная вероятность того, что PhysX был точно таким же говнокодом еще до его приобретения компанией NVIDIA.
 
Работа над ошибками
Если попытаться понять и разобраться, почему пример использования, скажем, кодера и декодера JPEG в исходниках занимает полтора мегабайта заглянув в эти самые исходники, то большая часть вещей встанет на свои места.
Во-первых, каждых технарь в каждом примере пишет свой трехкилометровый класс для парсинга командной строки. Почему? Потому что он не знает про Boost.Program Options.
Во-вторых, каждых технарь в каждом примере пишет свой парсер заголовков изображений в BMP-формате. Почему? Потому что он не знает про Boost.GIL.
В третьих, каждых технарь в абсолютно каждом своем приложении изобретает еще очень много разных велосипедов с квадратными колесами, потому что он не знает про STL, контейнеры, итераторы, алгоритмы, функциональные объекты, а также библиотеки Boost, решающие огромный спектр задач.
В четвертых, каждый технарь считает, что просто так взять и написать краткий и лаконичный консольный пример из 50-ти строк нельзя. Нужно обязательно создать MFC-приложение из нескольких десятков файлов и размазать по нему тонким слоем демонстрируемую функциональность, тем самым затруднив понимание сути предоставляемой функциональности на порядки. Видимо, технарям платят за строки кода.
В пятых, технарь слишком сильно увлечен производительностью и качеством выходных данных, чтобы успевать думать о пользователях с их проблемами.
В итоге 50 строк кода, которые могли бы наглядно продемонстрировать возможности библиотеки, превращаются в 50 000 строк абсолютно бессмысленной мешанины. Развитие среднестатистического технаря как программиста останавливается в тот момент, когда он научился писать программу, которая работает.
Для того, чтобы написать хорошую библиотеку, недостаточно просто хорошо разбираться в предметной области. Хорошая библиотека — это не просто решение задачи. Хорошая библиотека — это удобное решение задачи. Практически ни один технарь не представляет себе, какая бездонная пропасть лежит между этими двумя понятиями. Интеграция удобной библиотеки в хороший проект — это редко минуты, чаще часы, редко дни. Интеграция неудобной библиотеки в хороший проект — это редко часы, чаще дни или недели. При этом на саму интеграцию тратятся все те же минуты или часы, а дни и недели уходят на изучение, сборку и адаптацию неудобной библиотеки под нужды проекта.
Основная задача любого разработчика библиотеки (если не считать производительность и прочие очевидные вещи) — придумать удобный публичный интерфейс, то есть точку соприкосновения человека и библиотеки. Зачастую эта задача оказывается сложнее, нежели сама тематика предметной области задач, решаемых библиотекой. К сожалению, практически никто из технарей этого не понимает.
 
Правило
Хорошему технарю нужен хороший программист.

Оглавление
Статистика
© 2007—2010 Inside C++ Коммерческие услугиКонтактная информация