Лекция 2. Сборочные зависимости и инструменты работы с ними

Вы попали на цикл лекций UNEEX.

Мы подсократим тему разработки под Linux. У нас есть сервер, куда можно зайти по ssh и чего-то поделать, и вот ровно то, что можно делать по ssh, мы и будем изучать, хотя этим, конечно, разработка под Linux не ограничивается. В прошлый раз поговорили про IDE. Что нужны компиляторы, библиотеки, текстовые редакторы специального вида. Редакторов мы насчитали примерно 3. Про библиотеки сказали, что для разработки нужна ещё и devel версия библиотеки.

Тема этой лекции — сборочные зависимости.

Этот термин троякий или как минимум двоякий.

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

Во-вторых, это порядок сборки. Если вы будете собирать программу из одного файла, то cc o.c сделает один файл a.out. В реальной жизни такая лафа быстро заканчивается. Выясняется, что в вашем проекте не один и не три файла, а 500, и не только на C, а ещё на каком-нибудь Bison. В какой-то момент вы приходите к ситуации, когда у вас много файлов, из них получаются промежуточные объектники и потом уже бинарники. Вы сами порядок сборки ещё помните, а вот товарищу на словах уже не объяснить. Да и перекомпилировать все 500 файлов не всегда нужно и хочется.

Казалось бы, есть правила сборки — забей в shell-скрипт последовательность и собирай. Первая проблема такого подхода — скорость, вторая — порядок.

Итого, какое такое «всё» надо установить в систему, чтобы ваша программа собиралась?

В простом случае всё понятно — используется библиотека, надо поставить её и её devel версию.

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

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

Назовем эти проблемы.

  1. Определить граф зависимости ваших файлов, как исходных, так и промежуточных (генератов), и таргет — цель сборки. Порядок сборки влечет за собой понятие «зависимость по сборке».
  2. Проверка старшинства файлов. Пусть есть цепочка .y -> .c -> .o -> bin. Если объектный старше бинарного, то бинарный актуален. Если при этом .y более молодой — вся цепочка нуждается в перекомпиляции. Старшинство должно быть .y < .c < .o < bin. Соответственно, сначала строим граф, потом в нём проверяем старшинство.

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

А ещё, математика это хорошо, но ещё нужны какие-то действия. Стрелочки стрелочками, но за ними должны стоять команды. Как ни удивительно, это оказываются команды на shell.

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

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

Что сюда можно привнести реально удобного, чего не хватает?

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

  1. Чистка. Неплохо было бы удалять генераты, все или не совсем все. Есть одна проблема. У удаленного файла нет таймстемпа.
    clean:
        rm -f *.o
    Файл clean в результате этого не появится, да и зависимость никакая его не потребует. Поэтому
    .phony clean:
        rm -f *.o
    Указывает, что от этой цели не надо ждать появления целевого объекта.
  2. Переменные.

     -o$@ — target, значок, похожий на мишень. -с$< — source

    %.o: %.c
        $(CC) -c$< -o$@
    «Таргет получается из сорса вызовом команды $(CC)». Подстановка происходит тогда, когда выполняется команда — поздняя подстановка.

    Если нужна ранняя подстановка, то вместо = используется := . На сегодняшний день документация к GNU make весьма обширна. Много разных способов обработки текстовой информации при подстановке. Можно, например, написать так:

    SRC =  *.c
    OBJ = $(SRC:*.o=*.c)
    Превратить список сишников в список объектников. Такого барахла, очень удобного в очень редких (или не очень) случаях, там очень много.

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

    То есть, если сказать make CC=clang, то CC переопределится, даже если в make-файле было явное присваивание CC.

  3. Генерация зависимостей.

    .cpp всегда зависит от .h, если ашники есть. Поэтому такого рода зависимости не надо писать руками, их генерация автоматизирована.

Теперь к собираемости проекта.

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

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

autoconf

Чуть-чуть буду рассказывать про такую штуку, которая называется autotools, в народе также именуемая autocrap.

  • autoconf . Такой профиль, в котором все системнозависимые места стыдливо прикрыты макросами. Зачем? Привязка к расположению библиотек, привязка к компиляторам — всё отвязывается. Какой бонус и какой деградейшн? Make-файл не пишем, его генерит configure, а перед configure наличие инструментов проверяем отдельным инструментом. Деградейшн — в получившемся make-файле есть много останков роботов, и править его руками очень неприятно, но иногда приходится. Чего нам не сделает один autoconf, без товарищей? Не сгенерит зависимость от заголовков. Неплохо было бы, что бы по инклюду что-то догадывалось, какие заголовочные файлы нужны. Такой инструмент называется
  • autoscan
  • automake, А ещё хочется make-файл получать следуюшим образом — вот там у нас бинарник, вот там исходник, а сделайте нам make-файл. Для него правда нужно писать профиль, в котором высказывать разумные хотелки.

autoconf написан на M4, automake на Перле. Это можно читать, но, как это сказать, надо уметь.

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

Очевидно, это инструменты для поддержки разработки, для собственно сборки достаточно make-файла.

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

  • aclocal — создает кэш M4-макросов, актуальный для вашей системы.

Для тех, кто ещё не запутался, на Википедии есть граф запуска разных инструментов autotools и зависимостей их генератов.

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

В домашнем задании два пункта про autocrap — собрать GNU Hello. Задание повышенной сложности — справиться с сообщением, что у вас в системе autotools новее, чем то, что вы тут пытаетесь использовать.

autoreconf -fisv

  • libtool. Сборка библиотеки это отдельная штука. Это всегда системнозависящая вещь. Процедура компоновки и архивации это вообще дело даже не компилятора, а binutils, которых много разных.

Вопрос. Нельзя ли проще?

Зависит от того, чего мы хотим достичь. Если мы просто хотим ключи для сборки с конкретной библиотекой, или узнать где лежат инклюдники. Такая штука называется pkg-config. Кроме того, вышеописанный стек очень C-ориентирован. Если вы пишете на джаве, то это стремительно превращается в тыкву. В интегрированных средах обычно есть что-то свое. В джаве есть ant, в Ruby rake, и т.д. Причем они повторяют весь этот стек — и привязка к среде, и генерация make-файла по структуре проекта.

В противовес этому инструментарию изобретают и другие, для рещения того же списка задач, но более простым путем. Из известных лектору это scons и cmake. Приятная особенность — автотулзами можно пользоваться только после просветления, а писать проекты нужно уже сейчас. Так что цель — чтобы пользователь мог написать простой аналог am файла, специально обученная утилита зажует этот файл и начнет порождать сначала процедуры для проверки наличия зависимостей, параллельно генерить граф сборочных зависимостей, потом будет сгенерирован какой-нибудь бэкенд для системы сборки низкого уровня, например для make’а. Или проджект от Visual C++, или ещё чего. Это сильно повышае кроссплатформенность. Очень смешная страничка про cmake на русской Википедии (странно, что до неё не добралась википолиция.) Основное отличие scons, что он написан на Питоне, а cmake на C++. Из больших проектов Qt переехало на cmake. В итоге с точки зрения пользователя у вас все одинаково, а сборочные среды совершенно разные — код проектов на Qt может быть одинаковым для вин и лин (хотя тут ещё и особенность самого Qt, в котором есть всё своё).

out-of-tree сборка

scons и cmake загаживают исходники генератами. Кроме того эти генераты убирают в git ignore, и пока глазами не посмотришь, не поймешь, что там дочерта генератов хранится. out-of-tree — сборка в свежеочищенном каталоге. Это всё пляшет от идеи репродуцииромости сборки — когда скачали с github’а и хотим где угодно собрать.

Автотулзы очень крутые, с их помощью можно сделать out-of-tree сборку. А scons с cmake’ом не очень крутые, на них это делается само.

Домашнее задание — Собрать GNU Hello с помощью autocrap’а. Собрать пример на cmake. Задание для тех, кому неинтересно копипастить. В документации по terminfo и по Bison’у есть примерчики, их надо скомпилировать. Для программы на Bison’е написать make-файл. Если окажется слишком просто — написать cmake-файл.

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

Лекция 3. Отладка и трассировка

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

Довольно часто бывает так, что программа работает и делает чёрт знает что. Особенно часто это бывает с программами на C, в силу исторических его особенностей. Особенность C — любая синтаксически верная программа скомпилируется и будет выполняться. Никого не волнует, если вы два раза делаете переполнение, делаете чёрте что с указателями, заводите память и не отдаете её. Предполагается, что что вы написали, то и хотели. Например, хотел человек сложить ссылку на int с float’ом — наверное у него были какие-то соображения на этот счёт. Это, а также то, что некоторые люди забывают, что делает их программа на 200-й строчке, приводит к необходимости пошаговой отладки.

Пошаговая отладка

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

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

Мы договорились, что пошаговое выполнение у нас будет именно исходного кода на исходном языке. При этом мы стояли перед пропастью и сделали огромный шаг вперед. Одна строчка у нас превращается в кусок бинарного кода. Понятно, что для предоставления такой информации нам понадобятся сами исходники и дополнительная информация, которая будет толковать о соответствии бинарника и исходника. Debuginfo. По умолчанию оно не включено, потому что большое и нужно только для отладки. Что нам ещё нужно помимо исходного текста и debuginfo? Хорошо бы иметь ещё исходные тексты и debuginfo используемых библиотек. При таком раскладе мы можем вообразить себе пошаговое выполнение.

Кто главный враг такого подхода?

Параллелизм это враг номер два. Враг номер один — оптимизация. Хорошая оптимизация может превратить соответствие вашего кода ассемблеру в хорошо перемолотую тыкву. Попробуйте пройти в отладчике программы, скопилированной с -o3. Вы увидите, какие интересные там номера строчек, и не увидите многих своих переменных, вместо них будет optimized out.

На примере gdb давайте придумаем, что мы ещё хотим, кроме пошагового выполнения?

Брекпойнты. Выполняем подряд, но где-то останавливаемся. Точка останова — это возможность выполнять инструкции до какого-то момента и в этот момент остановиться. Командочка b. В gdb есть длинная форма команды и краткие алиасы.

b 25
b main

Остановиться на 25-й строке, остановиться в начале функции.

Условные точки останова — останавливаемся, когда значение по такому-то адресу равно тому-то или когда осуществляется доступ к какой-то ячейке памяти. Более общий вариант — когда некоторое выражение изменило своё значение. Это называется watchpoint. Останов, если выражение поменяло своё значение.

Чего ещё мы можем захотеть? Всего на свете, но не будем забывать, что мы используем не вариант с эмуляцией, а с родным процессором. В C++ ешё можно отследить исключения. Это называется catchpoint.

В каком-то далёком году в Линуксе научились хватать непосредственно системный вызов.

К этому списку добавляются команды по выполнению

  • run — запуск
  • continue — возобновить выполнение
  • next — следующая строчка вашей программы, если была функция, выполнится вся функция
  • step — зайти внутрь функции
  • final — при заходе в функцию на стек добавляется кадр. final — выполняться до тех пор, пока не начнётся возврат из функции.
  • stepi — ходить по машинным инструкциям
  • advance — выполнять до такого-то номера строки

Один из самых интересных и показательных инструментов, который можно применить если программа вылетела с ошибкой, это бэктрейс. Бэктрейс — это список стековых кадров со всеми вызванными функциями и их параметрами. Это очень информативно. Если происходит memory corruption, то сразу видно, в какой функции испортился указатель, хотя разыменоваться с плохими последствиями он может дальше. Для этого есть командочка

  • backtrace

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

После backtrace можно сказать list и понять, что творилось, когда программа упала. С помощью командочек up и down можно потом найти кадр, в котором все сломалось.

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

Можно напечатать 16-ричный дамп памяти командочкой dump. Некоторые любят это делать, ощущают себя хакерами.

Команда info — информация о брекпойнтах.

Всего в gdb около 800 команд.

Вот такой вот инструмент, пользоваться которым достаточно легко. Собираете программу с ключом -g

gcc -g a.c
gdb a.out
b main
run

Как передать аргументы? Сказать run аргументы, или запустить gdb.

Что можно сказать в довершение про gdb? Людей обычно пугает его нарочитая командлайновость. Лектор встречал разработчиков, которые написали много-много кода не пользуясь gdb. Есть врапперы.

  • ключ --ui,
  • cdb
  • ужасный ddd, написанный на Tk. Лектор им пользовался, но перестал.
  • интегрированные среды типа KDevelop

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

Так вот, разделяемая библиотека, загружается on-demand. Есть почти что два способа её загрузить

  • системный вызов dlopen() — открываем файл, смотрим заголовки, смотрим таблицу символов и начинаем вызывать. Это зло. Используется, только если ваша ОС совсем не загружает вам разделяемые библиотеки. Или когда у вас в ней символы, которые могут пересекаться с другими — например, когда у вас система плагинов. Отлаживать это очень сложно, потому что с точки зрения трассировщика это просто открытие файла.
  • ld.so — заставить системный компоновщик заниматься этим всем при старте.

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

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

Примерно тоже самое, но уже на уровне работы с заголовком библиотеки

  • nm

objdum и nm используются, чтобы посмотреть как устроено пространство имен и что есть в вашей библиотеке.

На самом деле, чтобы разобраться с зависимостями, из objdump и nm надо было бы городить пирог. Это неправильный способ. Правильный состоит в том, что в заголовке бинарника уже сказано, с какими библиотеками он слинкован, и посмотреть этот список можно командой

  • ldd

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

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

Можете совершить более тонкое издевательство над бинарником и подгрузить ему в память заранее вообще любую библиотеку LD_PRELOAD=/home/.../libhack.so ./a.out — сначала загрузить библиотеку, а уже потом принимать решение о подгрузке других библиотек. LD_PRELOAD осуществляет перегрузку нужных вам символов. На этой технологии основана куча веселых хаков. Например, под Линуксом нету 30-дневных триальников, потому что есть faketime, которая перегружает системную time и делает машину времени. Это странные вещи, есть и другие способы применения. fakeroot — запущенная программа думает, что запущена от рута. Тут правда более сложная система, помимо простой перегрузки операций ведется журнал операций. Если не пытаться читать данные из свежесозданных под ним устройств, то всё даже будет работать! Или это называется fakechroot?

  • ltrace — программа вызывает какую-то функцию, ltrace перехватывает и пишет диагностику — откуда что вызвано, а потом вызывает.

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

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

strace -efile
strace -f
strace -o out
strace -y

strace -y — чудесный ключик, который позволяет посмотреть не номер дескриптора, а имя файла.

У нас осталось пять минут на valgrind. Это подсистема трассировки и отладки, которая использует альтернативный подход — частичную или полную эмуляцию. Запускается виртуальная машина, заточенная под аккуратный учёт того, что происходит с памятью в вашей программе. Например, она может отследить, что на выделенный кусок памяти никто уже не ссылается. Кроме того, там есть работа связанная с оптимизацией — попадание/непопадание в кеш. Примерно такой же механизм для стека функций, который нужен для профилирования. Это обширный инструмент. В дз будет входить протыкивание примера на валгринде.

Для решения задачи непосредственного слежения за памятью есть ещё несколько инструментов, основанных на LD_PRELOAD

  • duma (бывший efence)
  • gperftools, бывшие google perfomance tools
  • gmalloc

Имя им легион.

Домашнее задание:

  1. Найти 10 самых используемых библиотек
  2. протыкать пошаговую инструкцию в gdb
  3. задание на gdb сексема

Лекция 4. Работа с исходным текстом

Мы не очень поговорили о семействе утилит для вынимания информации об исполняемом файле. Формат elf довольно непростой, исполняемый бинарник выглядит многосекционно, и для того чтобы с этим работать существует целая пачка утилит из которых мы упомянули только ldd, objdump и ...

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

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

  1. Как учитывать эти тексты? Как систематизировать и увязать проект в единое целое. Только таймстемпов недостаточно. На них можно ориентироваться если разработчик один, но это неудобный инструмент. Лучше придумать версионирование.
  2. Как обмениваться этими текстами с коллегами. Когда компьютеры были большими, а хакеры волосатыми, обмены происходили эпизодично — обменами ленточками на конференциях, между конференциями как раз успевали скомпилировать и подправить, да и программы были строк на 500. Но эти времена давно прошли.

diff patch

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

К diff прилагаются инструменты, например скрипт на ed, применяющий изменения к файлу.

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

Но, главная задача diff — сгенерировать патч, то есть файл с построчными различиями предположительного одного и того же файла, пригодный для превращения первого файла на второй.

ed-скрипт это очень тупая штука. Если удалили строку и вставили, то так и будет записано. Если применить такого рода ed-скрипт к изрядно измененной версии upstream, то он наделает многих бед. Было написано удалить 20-ую строку, он и удалит, даже если там совсем другое уже, а не то, что было раньше.

patch — программа, которую написал автор перла.

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

diff a b > p

В действительности очень редко когда далее делается

patch < p (->a)

На самом деле речь идет о совместной разработке. У соседа есть файл a', и к нему надо применить патч p.

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

618:
b=1
--- a=true
+++ a=false
c=4

Дифф в таком формате делает командочка diff -u

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

Есть несколько строгих правил применения патча:

  1. если контекст изменился, то патч не накладывается
  2. если изменились только пробелы, то можно попробовать их игнорировать (особенно концевые)
  3. Порядок применения патчей имеет значение.

Если патч наложился не целиком например на файл ash, то исходный файл переименовывается в ash.orig, модифицированный называется ash, и создастся ash.rej — неприменившиеся места. Куда должен был применяться отверженный патч нужно смотреть глазами. Чаще всего это связано с изменением контекста. Например, автор решил переименовать b в B.

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

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

Неудобство в следующем. Нету разумной технологии как на живых исходниках попав в ситуацию, когда несколько десятков режектов, очень сложно создать годный патч. Это трудно. В поддержку идет пакет patch-utils, Они смешные. Например у вас есть кумулятивные патчи (друг за другом), вы можете слепить их в один. Можно попытаться сравнить два патча. И многое другое. Позволяет работать с наборами патч-сетов.

Недостатки:

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

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

Версионирование

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

Следуюший кандидат rcs — добавляла ещё и полное хранение истории изменений в виде диффов. Был специальный каталог, в котором в виде диффов, начиная с нуля, валялись версии вашей программы.

Лектор всё это вполне ещё видел.

Зачем хранить дельту? Чтобы место занимать меньше. Когда компьютеры были большие, а программы маленькие, места для хранения были ещё меньше.

Всё это хорошо, но это индивидуальная разработка.

Как только речь идет о совместной разработке сразу возникает куча вопросов.

  1. Хранение кода. Где? Раньше у нас было просто дерево каталогов, но теперь надо чтобы несколько людей хранили в одном месте и видели изменения друг дружки. Как только возникает вопрос хранилища, возникает вопрос о том Как хранилище использовать. Теперь файлы общие, а не ваши личные.
    1. Права доступа.
    2. Но этого недостаточно. Дисциплина работы с хранилищем — и дисциплина оформления кода, и то, кто, когда и как помещает код в хранилище.
  2. История разработки. Не только как и когда модифицирован текст, но и кто его модифицировал. Один коллега подарил смешную девайсину на светодиодах и рассказал, что она у них светится красным светом, когда определенный товарищ комитит в репозиторий и надо оттуда это срочно вычищать.

Мы описали функциональность version control system. Их много, самая старая cvs. Она немного несет в себе болезнь нулевой версии — её придумывали в процессе разработки. Лектор пользовался cvs,но понял, что не постиг дао и слишком часто ломал репозиторий. Более современная реинкарнация — svn. Пытались сохранить воркфлоу cvs, но избавится от легаси.

Работа с cvs выглядит примерно так.

Сначала посмотрим как на ней выглядит малый цикл разработки, хотя он к ней плохо применим.

  1. Редактирование.
  2. Достижение критерия У вас есть некая цель, которую вы хотите достигнуть. Когда критерий достигнут, подзадача решена, вы должны зарегистрировать изменения.
  3. Регистрация изменений.

Большой цикл выглядит так

  1. Обновление рабочей копии
  2. Редактирование.
  3. Достижение критерия
  4. Регистрация изменений.
  5. Обновление хранилища.

Последние два пункта должны быть атомарны. В централизованных системах контроля это операция атомарна.

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

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

Существует специальная дисциплина, уменьшающая количество конфликтов, про это много написано, а будет ещё больше.

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

Любая технология передачи данных ассиметричная может быть описана как pull или push. cvs это push технология. Основная приводящая к изменением операция — заталкивание.

Можно же помыслить и другую технологию, основанную на pull. У каждого свой репозиторий. Что-то на хакал и звонишь — втяни изменения, посмотри.

Пример push технологий tfs, source safe, svn

При pull-технологии вырисовывается следующее:

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

"Заплатите им деньги, они помержаться"

Bazar, git, mercurial, darks

Напоследок про git.

Про git с практической стороны

git-clone и указываете адрес хранилища. После чего у вас создается полная копия. Гит хранит именно объекты, причем все. Для этой цели он считает контрольную сумму файла и делает это его меткой. У него есть дерево таких меток, и вычисляет разности между такими деревьями. Хранит он вообще всё. Между деревьями можно переключаться.

git pull

Похакали

Сказали add на те файлы, которые хотим добавить в дерево

Потом git commit (регистрация)

git push (в удаленный репозиторий)

git merge tool запускает редактор, который открывает сразу 4 файла.

Недавно лектор открыл для себя гит как инструмент работы с совсем любыми исходниками.

Развернули

git init
git add .
git commit -m "Init"
git tag start
зверски хакаете, понимаете, что сделали полную чушь, 
git reset --hard start
git clean (удалить временные файлы, для которых не было адд)

Гит помнит всё.

Как отдать патчи пользователю

git-format patch start — от текущего сотояния до тэга старт преврати каждый коммит в патч

Потом используя это патчи можно пересобрать всё дерево заново git-am

Зачем превращать апстримные исходники в гит.

git rebase пытается старый набор патчей переприменить к новому дереву исходников. Нужен, что переписывать дерево исходников — была ветка от одного места, мы её приварили к другому.

git rebase --interactive Очень удобная вешь.

Как организуется информационное пространство. Нужно иметь пресловутый сервер публикации.

github

gitorius

Можете организовать самостоятельно, например с помощью gitolite.

Если процедура работы с хранилищем определена чётко, то над процедурами работы с информационным пространством народ ещё экспериментирует.

Лекция 5. Дисциплина оформления и ведения исходного текста

О чём мы успели поговорить. Начали с момента — давайте создадим окружение для разработки. Как вообще идёт сборка проекта. Как устроена сборка более сложных проектов. Потом народился разговор об отладке, потому что выяснилось, что большие программы надо трассировать и отлаживать. Потом наш проект вырос до того, что потребовал совместной разработки и версионирования и мы поговорили про работу с исходным текстом разными способами. Следующий шаг естественный — если команда работает над кодом и есть более опытные, то остальные к ним прислушиваются. Соответственно, когда люди пишут патч, они показывают более опытному, и тот либо принимает, либо не принимает и либо объясняет почему, либо нет, и пишет сам. За последние 20 лет тирании стало поменьше, объяснений побольше. При совместной разработке возникает желание научить и научиться писать код правильно и так, чтобы друг друг не мешать. Хотя в некотором плане это аукается на индивидуальную разработку, чтобы понимать, что было написано ранее, например.е

  1. Дисциплина совместного использования хранилища (особенно в централизованном хранилище).
  2. Дисциплина оформления и ведения исходного кода.

Дисциплина работы с хранилищем

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

  1. Одно изменение — один коммит.
  2. Двухчастный commit message. Есть два противоречивых требования к коммиту: описать все изменения и сделать это в одну строчку. Вы могли исправить несколько частей кода для решения одной проблемы. Соответственно, коммит месседж делится на резюме и подробности. В гите это уже чуть ли не стандарт.
  3. Что касается самого процесса разработки и создания веток, то дисциплин чёртова прорва, но одну вещь надо отметить — трёхпоточную разработку. Трёхпоточный режим разработки состоит из трёх веток: development (в которой идёт разработка), testing (ветка для тестирования того, что должно стать релизом) и release (ветка релиза с допиливанием багов обнаруженных после релиза). Называться они могут по разному, но это не важно. Речь идёт о том, что есть то место, куда вы без зазрения совести добавляете изменения, то место, где вы добиваетесь совершенства, и то место, где вы уже добились совершенства.
  4. Когда ваш проект достаточно большой нужно устраивать дисциплину не только личного хранилища, но и мёрджа. Граф слияний. Есть некая вершина пирамиды, куда отвественный подтягивает изменения, подготовленные лейтенантами, каждый из которых отвечает за свою подсистему и подтягивает изменения из других мест (не факт, что разных). Обратите внимание, что на этой картинке у нас рисуется ориентированный граф с некоторыми свойствами: у него нет циклов и у него есть уровни. Этой картинки стоит придерживаться, если вы хотите получить качественный продукт.

Дисциплина оформления исходного кода

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

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

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

  • Цель — Зачем (мы что-то делаем)?

  • Задача — Что (надо сделать)?

  • Решение задачи — Как (это сделать)?

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

Что такое эта продуктивность? Из чего она состоит?

  1. Индивидуальная продуктивность.
    Комментарии нужно писать не только для других, но и для себя. Не надо писать программу в одну строчку, даже в две не надо. Просто чтобы сами смогли прочитать. Существуют нормы, которые хороши просто потому, что они хороши. Предупреждение новичков о возможных ошибках.

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

  3. Доступность сообществу.
    Когда речь идёт уже о достаточно большом сообществе, код надо писать с оглядкой на то, что его будут читать и модифицировать другие люди.

    1. KISS — Keep It Simple Stupid.
      Тупой код надо писать не потому что другие люди тупые. Наоборот, они могут быть очень острыми. Пересечение знаний гениев может быть не большим, и надо в него поместиться.

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

  4. Учёт специфики проекта.

    1. Размер и ротация сообщества.
    2. Требования к надежности кода.
      Надо хорошо себе представлять, что, условно говоря, любой код ненадёжен. И как следствие: комплекс мероприятий по улучшению надёжности кода может быть бесконечно большим. И требовать бесконечных ресурсов. Где остановиться зависит от того, каковы ваши реальные потребности. Если вы почитаете правила написания драйверов к современному Линукс-ядру, забыв что это Линукс-ядро, вам многие веши покажутся оверкиллом. Но для ядра они имеют смысл.

    3. Специфика языка программирования
    4. Специфика предметной области (например, программирование драйверов с магическими числами).

Давайте теперь распилим это требование к продуктивности в другой плоскости. Что имеет смысл проверять на предмет того, надо это вносить в coding style policy или не стоит? Что имеет смысл фиксировать или не фиксировать?

  1. Внешний вид кода. Отступы, соглашения о переносе длинных строк и т. д.

  2. Именование. Венгерская нотация. Человек, который её придумал [Чарльз Симони] не думал хранить в названии переменной её тип, глупее этого только хранить в имени значение. Он пытался внести в имя переменной семантику использования. Но большинство индус-триальных программистов не понимали, что такое семантика, и писали туда тип.

  3. Не использование особенностей языка программирования.

  4. Комментарии.

Приехала программа к компании, которая её поддерживает не первый год. Программа падает. Выясняют, что падает при выводе непонятного байта в непонятный порт железяки. И в этой строчке есть такой комментарий ;JSB RIP*/ Начинают искать автора. За несколько месяцев находят. Спрашивают его, что это значит. Тот говорит, что не помнит. «А, помню! Обратите внимание на тот байт, который передаётся в порт. Если перевести его в десятичный вид, то получится год смерти Иоганна Себастьяна Баха. Мне пришло в голову такое замечательное совпадение и я его зафиксировал». Плохой метод регулировки комментариев -- процентное соотношение кода и комментариев. Плохо тем, что люди начинают писать генератор комментариев -- перед каждым циклом писать "Это цикл по переменной и". Не руками. Получаем прекрасный код с заданным процентом комментариев.

  1. Разбиение кода на модули.
    Например, что касается функций, если она здоровая и решает сразу несколько задач... В таком случае никакого code reuse не получится.

  2. Повторное использование.
    Вы пишите программу не один. Скорее всего архитектор уже подумал, что много функций написано. Не надо писать их самому. Если пришло в голову изобрести устройство для быстрого перемешения по ровным поверхностям — два колеса, палка, седло — возможно оно уже придумано. Даже если вы название уже придумали, ну, скажем, «убыстритель ног», то вполне возможно, что оно уже существует. В Qt, например, все подзадачи уже решены. Я уже не говорю про MSDN, где есть в несколько раз больше, чем всё. Надо только уметь по нему искать.

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

Linux Kernel coding style

Рассмотрим в качестве примера уже упоминавшийся Linux Kernel сoding style.

Я готов подписаться под каждым словом и поведать всему миру и всем программистам на Си, что счастье есть. Всем искренне рекомендую прочитать этот документ. Во-первых, он читается как литературное произведение. Во-вторых, содержит много полезных рекомендаций. Первый совет там — распечатать и сжечь копию GNU Coding Style Guide. Хотя там достаточно серьезная специфика, но это приличный позитивный документ, который стоит прочесть.

  1. Отступы делаются только табуляциями шириной 8 пробелов, общая ширина строки не больше заданной. На Java, например, это было бы слабо возможно. Программистам на Джаве, которым трудно соблюсти эти требования, стоит понять, что это всё-таки не Java, а C, и что этот код будут читать сотни людей.
  2. Отдельный разговор про то, как ломать длинные строчки.
  3. Как раставлять пробелы и скобки. К каждому пункту прилагается объяснение, почему рекомендуется делать именно так.

Закончили с внешним видом.

  1. Достаточно коротко и разумно, рационально описаны правила именования всего и всея в ядре. Там много подпунктов. Наименования должны быть которкими и ясными. Считается хорошим тоном не смешивать большие и маленькие буквы. Hungarian notation is forbidden.

Потом перескакиваем на особенности языка программирования.

  1. Отдельным пунктом и отдеальную ненависть у Грега вызывают typedef'ы.
    Они используются ровно для двух вещей. Первая: перегрузка int'а (во что-то разумное с известным размером). Вторая: написание безумных никому непонятных типов, про которые даже не известно, ссылки они или нет.

  2. goto удалить мусор
  3. По части комментариев. Первое: Это должен быть очень небольшой текст, который доносит до народа всего две мысли: _что_ делает функция и _зачем_ (если это не очевидно). В комментарии не должно быть написано, _как_ работает ваша фукнция. Второе: Комментирование функций, структур и юнионов следует делать по заранее определённому шаблону, чтобы можно было специальным инструментом их оттуда вытащить. Не doxygen’ом, а более простым инструментом на базе grep’а. В те времена никакого doxygen’а ещё не было. А цели документирования исходников ядра гораздо более узкие, чем те, которые можно достичь с помощью doxygen'а.
  4. Что делать, если вы написали плохую, негодную с точки зрения функцию. Отформатируйте её скриптом и если вас устроит результат, оставьте как есть.
  1. Пункт относится уже к специфике разработки ядра.
  2. Отдельный пункт говорит о том, какой обвязкой сопровождать структуры данных. Любая глобальная структура данных должна сопровождаться реф-каунтером.

Дальше пошла всякая специфика.

  1. Как оформлять макросы. Ну например, там сказано, что не надо пихать в макросы кусочки, которые меняют ход выполнения программы.
  2. Если вы хотите обеспечивать какой-то вывод событий ядра, то не надо самому это делать. Есть kernel messages.
  3. Отдельный разговор про то, как работать с памятью.
  4. Отдельный пункт Грег посвятил использованию/не использованию inline функций. Здесь ситуация не такая однозначная как с typedef'ами. У вас должны быть резоны на использование inline функций.

Почему inline может быть взломан?

Я один пункт проворонил:

  1. Спецификация на собственно функции. Смотрите какая штука. Язык программирования Си имеет свои особенности. 0 — это false, а 1 — это true. Если же идёт речь о выполнении функции, то всё наоборот. Предикаты возвращают ноль и не ноль, а действия возвращают success или errno.
  2. Следующий пункт называется: пожалуйста, используйте kernel macros.
  3. Ну ещё парочка примеров. Не вставлять директивы текстовых редакторов в код (например настройки ви).

Не используйте magic numbers. «В порт вывести число 245». Что оно означает? Чёрт его знает. Если вы используете какую-то константу, её имя должно быть дискриптивным. Для этого есть дефайны.

Заключение

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

Почему я начал с того, что не дочитал почти ни один другой coding style guide до конца? Потому что там люди пытаются стандартизировать гораздо больше. Показателен в этом смысле BSD coding style. Люди явно вознамерились написать, как правильно писать на Си. Или Google Style Guide — как писать по-гугловски на многих языках программирования. Подозреваю, что эффективность таких вот ненормально детализованных дисциплин зависит не от содержания этих дисциплин, а от мотивированности разработчиков на их соблюдение.

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

Напоследок анекдот: В 2011 году компания Microsoft попала в первую десятку (была седьмой) по количествую коммитов в ядро Линукс. HyperV надо было поддерживать. Код, который реализует его поддержку в ядре Линукс существует не первый и не второй год. Но то ли люди которые его писали были не очень грамотные, то ли ещё что, но в ядро его не принимали, потому что стайл гайд никак не соблюдались. И так несколько лет. После того, как они удосужились ознакомиться с Linux Kernel coding style или кто-то им подсказал, и они привели свой код в соответствие с ним, весь этот код, что они накодили за несколько лет, в 2011 году попал в ядро. Чтобы закрыть тему — крупные корпорации действительно являются крупными коммитерами в ядро, просто потому, что им надо поддерживать их железяки.

Лекция 7. Контроль качества программного продукта

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

Как бы нам убедиться, что наш продукт нормальный? На прошлой лекции было про то, как убедиться, что код нормальный -- писать в соответствии с полиси и стайлгайдом, и т. п.

Определим места, в которых надо убеждаться в нормальности.

  • сборка (собираемость и журнал). Если программа не собирается, то она неработающая, ваш капитан. Предположим, что программа собиралась, но с предупреждениями -- ссылочку не так закастили, инклюдик забыли. Этап сборки можно считать успешным, если программа собралась и журнал сборки (ворнинги) не стал хуже. Если он стал хуже, то может программа стала хуже, может инструменты сборки стали требовательней. Соответственно, нужно смотреть, как и насколько ухудшились. Мы можем, конечно, писать -Wall так, чтобы не было предупреждений. Но далеко не все проекты компилируются с -Werror, и в таких случаях хорошо бы следить за численными показателями. Представьте ситуацию -- идет разработка большого программного продукта. Собирается он долго и не на всякой машине. Сидит программист, коммитит. Как часто надо пересобирать? Надо ли это делать на машине программиста? Вопрос сложный и без однозначного ответа.
    • когда пересобирать
    • регрессия журналов Решаются эти вопросы полуавтоматически. Но возникают проблемы вида 1000 ворнингов можно, а 1001 нельзя. В любом случае, контроль собираемости это хороший инструмент контроля качества, показываюший, что хоть какое то качество имеется. Проблема -- многие люди на нем останавливаются. Программа собирается, запускается, значит хорошая. Часто этим страдают мантейнеры, которые собирают по привычке, но не пользуются собранным.
  • Интеграционное тестирование. программа собралась, предосудительного в логе не нашли. Какие могут быть еще вопросы некачественной сборки, если бинарник получился нормальный? Помимо бинарника есть ещё окружение. Нам же надо чтобы оно задеплоилось в какую-то среду и стало работать. Если так получилось, что собранный нами бинарник требует не таких библиотек, какие есть в той среде, то это негодный бинарник. Соответственно, пункт второй -- интеграция. Получившийся продукт может плохо интегрироваться в среду, для которой предназначен. Палка о двух концах -- можем захотеть библиотеки с другим апи, или, если мы библиотека, то можем изменить апи. Этот кусок называется
    • системное тестирование. Если вы что-то запускаете из своего пп, то оно обязано существовать. Чтобы отследить подобного рода ухудщение качества недостаточно запустить программу в тестовом окружении, потому что тестов ограниченное число, оттестировать всю функциональность невозможно. А вот наличие аби можно полностью проверить. Кроме свойств продукта на этом этапе вы должны знать свойства среды деплоймента. Когда вы делаете сборочное тестирование вы худо-бедно взаимодействуете с сборочным окружением, а в системном тестировании уже окружение деплоймента.
      • отслеживание зависимостей. Первое с чем вы сталкиваетесь, работая с линуксом -- зависимости. По сборке, по запуску, по наличию файлов, итд.
    • отдельный интерес представляет изменение в аби, которое невозможно без специального инструмента. В сизифе введено понятие зависимости на хеш используемых функций. Даже это не помогает нам полностью отследить изменение аби -- может измениться не имя, а параметры. Эти изменения надо как-то проверять. Вообще говоря, встроенных инструментов в трад. яп нету, хотя в виртовских, типа оберона, они были, хотя и немного странное -- версионирование и при несоответствии версий конфликт. Похожий механизм сейчас для модулей ядра. Есть сейчас такие ребята из ИСП, которые занимаются верификацией кода сложных программ, и есть у них 20000 строк кода на перле -- инструмент ABI Compliance Checker -- у них три автора -- Хорошилов, Рубанов и тот, кто всё это написал. Эта удивительная хрень берёт два h файла, и сравнивает ABI. Мощный, интересный инструмент. Достаточно формализуемая задача -- сравнить два списка сишных хедеров. Несколько хуже обстоит дело с ситуацией, когда у вас среда, в которую вы планируете помешать продукт -- большая. Например сизиф. Собираете новый пакет, он не должен мешать остальным 12000.
    • глобальное пространство имен, namespace. Для линукс разработчиков такая проблема вполне разрешима, благодаря специальной процедуре помещения туда. Необязательно, чтобы это пространство было непротиворечиво, но вы своим проектом не должны это ухудшить. Идеально это тоже не работает, потому что когда выходит новая версия библиотеки, она вполне может осознанно ломать аби. Пример контроля за пространством имен это любая современная сборочница линукс-дистрибутива. На одну такую (ginar-builder) дана ссылка на странице материалов лекций. Это не очень большая пачка шелл-скриптов, можете почитать.
  • Работоспособность.
    • приемочное тестирование. Программа запускается, а мы в нее начинаем тыкать. Собрали, теперь запустим и покликаем. Главных проблем две
      • написание достаточно глубокой методики тестирования. Программа обладает многими свойствами, свойства вступают друг с другом в суперпозицию и дают факториал новых свойств. Описать все случаи использования невозможно. Когда строим программу испытаний/методику тестирования мы себя ограничиваем, выкидывая примерно 100 процентов возможных ситуаций, оставляя конечное количество типичных юзкейсов. Задача абсолютно неавтоматизируемая. Хотя, если ваш продукт является библиотекой, там хотя бы есть костяк -- пишем программу, которая использует все функции различными спосоами.
      • тестовое окружение. Если не принимать дополнительных ограничений автоматическое тестирование или невозможно, или ужасно. Монстры, обещаюшие протестировать полностью приложение ужасны, и как правило накладывают ограничения на расположение и название кнопочек, итп. Приемочное тестирование в общем -- не очень автоматизируемый. Хотя в частных случаях вполне автоматизируем. Например, математическая библиотека. Или установщик альтлинукса -- программа с конечным количеством состояний. Тот же установщик -- несколько формочек с органиченным количеством кнопочек и небольшим количеством вариантов правильного функционирования, любая нештатная ситуация означает нерабочесть. Итак, относительно просто тестируются
        • библиотеки с ограниченным количеством функций
          • интерфейсно малые приложения
          • интерфейс командной строки. С ним можно забавляться как хочешь. expect - специально изобретенный для этого язык программирования, на нем написано много инструментов, например DejaGNU. Экспект это специальный язык программирования, но и самопальных подобных причиндалов. Expect на tcl, Empty на шелле. Отметим, это не просто для пассивного ввода-вывода, но и иммитация терминальной линии, когда пользователь будто бы вводит какие-либо командны.
          • Что касается UI. Существует очень много инструментов, нажимаюших на кнопочки в виндоусе. Подобного рода инструменты есть и под линукс. Xautomation, xpresser. Общее свойство фиговин, кликающих на кнопочки в уи. Какая главная проблема? Как узнать, что эта группа пикселей на экране это кнопка. Один способ решения - знаем на чем написано приложение, лезем ему в голову и ищем координаты виджетов. Или ищем на экране заданную картинку. Это хорошо, если шрифт один и тот же, и кнопочки серые. Плохо, если дизайнер перегенировал пнгшки? Тогда xautomation перестает работать. Поэтому придумали кнопку -- отключить темы вообще, совсем.
          • Ещё один пример жестко детерминированного интерфеса -- веб-интерфейсы и сетевые приложения. Инструментов много, нормального лектор не нашел. Тема очень горячая. Каждая четная собака хочет веб-магазин, а каждая нечетная предлагает инструменты для этого. Selenium. Приятность этого дела, что хтмл очень простой, красота вся наводится отключаемым цссом.
    • попробуем отступить на шаг назад. Не могли мы методику тестирования как либо породить? Порождение тестов. Оказывается, можно. Все тот же товарищ из ИСП написал небольшой скрипт на перле на 20 000 -- API sanity cheking. Если предыдущий скрипт был ещё наука, то тут чистое искуссто. По имени переменной догадаться о ее семанике, по типу структуры о ее содержимом. Но как ни странно эффект массовости дает о себе знать. В библиотеке из 1000 функций на 100 он даст неправильный тест, а на 2 найдет ошибки. Проблема -- иногда дает false negative. Так же, для порождения тестов по исходному коду код должен быть специальным образом оформлен, причем образом, удобным для генератора, а не для программиста.
    • Разработка через тестирование, по русски - test driving develoment. Идея -- когда пишем программу, пишем, как она должна работать. Сначала делаем тест, который не работает (ещё до реализации функции). Когда тест не работает -- реализуем функцию, доводим до того, чтобы тест работал. Точно также, как Literate programming -- способ изложения программы на родном языке, макроподстановкой переводящийся в код. Можете зайти на страничку википедии. Там живо описаны бенефиты такого подхода. Недостатки -- люди не любят поступать правильно. Второй -- в таком случае удобно программировать на всяких контрактных языках типа эйфеля. На низкоуровневых языках вам придется перестраивать голову. Третье, скорее свойство -- оверхелм -- сначала вы будете писать заглушки. Хорошо тем, что вы сразу делаете некую архитектуру. Плохо -- что эта архитектура наверняка походу изменится и их придется переписывать.
    • Модульное тестирование. Однако есть компромисс, -- юнит тестирование как таковое. Сначала код с юнит тестами, потом независимый системный тест, а потом приемочный. Тест дривен подход будет относиться только к отдельным компонентам, а не ко всему продукту. Резко обрезанный сверху и снизу вариант тест дривен. Про юнит тестирование знают все промыщленно программировавшие люди. Если не знают, то они несчастны. Дошло до того, что образовалась целая идеология модульного тестирования, которая называется xunit. Её сформулировал какой-то человек, программировавщий ещё на смолтолке.
      • есть понятие test case, что тестируем (?)
      • В неё входит окружение, при котором тест эффективен. Нужно подготовить объекты, с которыми работает функция. Если есть файлы, они должн быть на месте. Fixture
      • Test Suit. Внутри одного окружения можно продлеать несклоько тестов.
      • Assertion. Замеряете параметры до, после, и смотрите, оправдываются ли ваши ожидания.
      • Тирдаун -- свертка окружения. Ничего экстроординароного и сверхумного в этой идеологии нет, но надо было как-то формализовать тестирование отдельных кусков программы. Основные инструменты юнит-тестирования
        • CUnit, Check (c)
          • boost (c++)
          • unittest (python). Хотя даже для питона их сотни, потом что как именно делается тот же тирдаун идеологией не формализовано
          • jtest (java) Изобратать 10001-ый инструмент не стоит, лучше взять существуюшее. Итак, юниттесты вклиниваются в тестирование посередине. Тестируется не вся программа, а отдельные компоненты.

Как все это собрать в единую кучу? Много инструменов, все разные, как организовать непрерывный процесс разработки. Перечислим стадии.

  1. Верификация исходного кода.
  2. Сборка. В процессе сборки -- модульное тестирование.
  3. Формирование дистрибутива (чего-то, что будет передано и где-то установлено)
  4. Деплоймент. Не обязательно это инсталяция, мало ли, как вы помещаете свой код в рабочую среду. Процедура введения кода в строй. Как именно это происходит не важно, важно, что он может не пройти.
  5. Приемочное тестирование функциональности
  6. Публикация.

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

Рекомендуется поглядеть на buildbot.

Замечание. Этап сборки не должен происходить на компьютере программиста. Черт знает, что программист себе поставил, во-первых. Во-вторых, не дело программиста собирать проект под 8 разных архитектур, например. Это дело сборочника, который должен собирать и доносить до программиста проблемы.

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

Лекция 8. Инструментарии

В воображаемом пути изготовления продукта мы уже дошли до всяких модульных тестирований и самопроверки, воркфлоу по выпуску — в общем, большой проект. Маловероятно, что мы его написали с нуля на языке цэ. Линукс как конструктор от юникса как конструктора отличается — современное состояние свободно лицензируемого софта таково, что мало мальски актуальная инструментальная задача имеет существенно более одного решения. Вам может показаться, что библиотеки делают немножко не то, но ситуация, когда есть тех задание о разумных вещах и нет инструментов почти не случается. Гораздо чаще заказчик считает, что у него феерически новая задача, а потом оказывается что для её решения надо прикрутить обычную нейронную сетку к обычным датчикам, обычной базе и обычному веб интерфейсу. Все эти задачи имеют очень много готовых решений — десятки и тысячи. Встает проблема не разработки, а поиска и выбора подходящего инструментария. Это сильно отличается от проблемы реализации алгоритмов, встававшей 30 лет назад. Кстати, видимо этим и объясняется качество научного кода — люди, которые пишут на C в помощь своей теореме, они считают, что пишут это впервые. В хорошем исследовании так и будет, процентов для 10 кода. Но многие ученые пишут с нуля всё.

Выбор инструментария

За последние лет 10 проблема выбора инструментария изменилась от игры по интересам, когда ты заходил на freashmeat и там перечислялись все новости про свободное ПО, эта задача резко усугубилась и становится knowledge deeping.

Помимо всем известного sourceforge есть googlecode, есть github и gitorius, bitbucket, savanna. Различные среды программирования имеют свой собственный портал — Python, Ruby, Perl.

Есть три класса

  • хостинги проектов, тип sourceforge
  • хостинги с поддержкой работы с исходным кодом, типа github
  • целевые хостинги, например по языкам программирования

Для выбора можно воспользоваться ***меркой, например, ohloh.

Есть две вредные тенденции, побуждающие разрабатывать свободный инструментарий:

  • NIH. То, что не написали мы с братом, это по определению отстой, и пробовали мы читать этот код — точно отстой. В корпоративном секторе это не сильно отличается. У самого отношения к свободному коду как к потенциально опасному есть рациональное зерно, но его доля в мотивации создавать свои велосипеды очень мала.
  • люди, которые тренируются. Любая инженерная деятельность, связанная с разработкой, провоцирует изобретение велосипедов для повышения скилла. Если залезть на тот же самый sourceforge, вы увидите, что там много таких заброшенных проектов.

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

Признаки годного проекта

  • размер и активность сообщества — закон больших чисел. Главный показатель удобства работы с инструментом — не качество и количество кода, а качество сообщества. Чем больше людей, тем больше вероятность найти единомышленников со схожими задачами.
  • что у них с release engineering. Насколько люди заинтересованы в том, чтоб их кодом кто-то пользовался. Готовятся ли анонсы, есть ли багфиксы. Иногда бывает так, что команда профессионалов пилит какой-то продукт и на всех плевать хотела, пушат в свой гит или свн, тэгов не ставят, и т. д. Это не самые удобные ребята для взаимодействия. По русски пункт будет — работа с выпусками. На сегодняшний день это довольно частая тенденция. Например, у людей был сайт, они на нем выкладывали версии продукта. Потом открыли для себя гитхаб, забили на сайт, релизы делать перестали, про теги в гите не знают. Был хороший сайт, стало никакого. И надо выяснять, с какого commit id надо собирать, чтобы получился годный код.
  • возраст. Например, на сурсфорже много проектов, которые прожили год или полтора. Дипломные работы, что ли?
  • только после этого идет качество кода. Почему это не первая причина?
    1. О вкусах не спорят
    2. Инструмент надо выбирать так, чтобы ваше вмешательство в нем было минимально. В таком случае качество кода для вас не слишком важно. Если вы не видите совсем уж феерию.
    3. В старых проектах код ужасный — хакеры писали 30 лет назад. Они все тогда только открывали, но были гениями. Это ужасный код с костылями, но за 30 лет все ошибки оттуда выковыряли и замазали.
    4. Не забудьте про лицензию. В нашем мире лицензионная чистота дело очень важное.
    5. Весьма важен язык программирования. Вряд ли вы умеете программировать на всём.

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

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

Опишем цели создания комбайнов

  1. сделать набор инструментов кроссплатформенным. Довольно большое число инструментов все таки предназначены для оперделенной платформы, если это не какие-то математические библиотеки. А хотелось бы не просто кросс платформенность исходного кода обеспечить но и кроссплатформенность процесса разработки, что важнее. Пример — нужно написать программу одновременно под вин-лин-мак. Вы можете либо писать три программы и одинаково их называть. Общим будет только платформонезависимая логика, а всю привязку делат средствами ос. Этим вы умножите работу почти в три раза и получите необходимость иметь три набора разработчиков. Либо вы используете кроссплатформеные библиотеки и получаете проблему, что оно по разному собирается на разных платформах. Если вы хотите унифицировать этот процесс, то надо искать не только набор библиотек решающий задачу, но и набор инструментов решающий все подзадачи, возникаюшие в процессе разработки.
  2. Переписать стандарт. В ос с которой вы работаете есть некий программный стандарт на то, как можно с ней обшаться. Стандарты разные для разных ос, зачастую старые и не все охватывают. Например, позикс. Первый способ — писать новый стандарт. За 15 лет как раз успеете. Второй способ — писать инструментарий, вытесняющий стандарт дефакто. Идея — дать новый подход к задачам, которые нэффективно решаются стандартными инструментами.
  3. Информационное пространство. Вам надо смотреть за размножением разноцветных ежиков и публиковать результаты. Подзадачи — работа с датчиками, складывание результатов в базу, уход за ежиками и логика, публикация. Решается несколькими инструментами. А вот если у вас офисное приложение, которое работает с диском, изображениями документами, или программа настолько сложна, что требует объектного планирования, в общем когда задача слишком сложна и распадается на подзадачи из разных областей, то решать её большим количество разношерстных библиотек чревато — теряется унификация и связность информационного пространства. Свойства из этого информационного пространства.
  4. общая идеология разработки. Хорошо тем, что те, кто будет в команде будут понимать, как будет выглядеть кусок кода, написанный другим человеком.
  5. Основная цель разработчика инструментария — разработать инструментарий. Они хотят потом на нем программировать. Основная идея сделать его не только мощным, но и удобным к освоению — привлечение сообщества. Если мы имеемдело со свободной разработкой, то немаловажным подспорьем будет привлечение в него программистов, которые будут помогать в разработке. Или люди говорят — мы полгода будем писать инструментарий, а потом за год напишем вам на нем проект.

Давайте начнём снизу. Пример обшего инструментария для программирования на C

  • libc. С точки зрения программиста это обёртка над системными функциями. Правильно завернутые системные вызовы во вкусной обертке. Она является инструментарием в смысле 40-летней давности. Аналогичное API есть в любом языке программирования.
  • шаг вверх, всё ещё на языке C. glibc. Содержит реализацию разных типов данных, строковые функции, случайные числа, регулярные выражения. Пауза. Зачем делать собственные строкоые операции? Надежность и кроссплатформенность. Недостаток строк в Си — это сишные строки, последовательности байт с нулем в конце, и вообще массивы. А хотелось строки переменной длины, и уметь из делить, тасовать итп. Отдельно — работа с интернационализацией(адоптация к другим языкам) — классический пример расширения стандарта. Отдельные примитивы для работы с памятью. Зачем? Кто отменил free и malloc? Рефкаунтеры. Поддержка атомарных операций. Процессы, нити, таймеры — чистый замах на кроссплатформенность. Встроенные лексический и синтаксический анализатор. Не хочется писать читалку ини-файла. Переписывается ввод-вывод. Если взять существуюший input output в ОС — то окажется, что они очень сушественно отличаются. А данные передавать надо, да ещё и кроссплатформенно. Далее — отладка и диагностика. Накопление и вывод ошибок, процедуры. Когда вы пишете много, без этого не обойтись. ЗАпускаете приложение гткщное, оно начинает сыпать ассертами, но продолжает работать.
  • поверх glib есть ещё gobject. Там есть и классы, и контейнеры, и метаданные. Относительно полноценное ооп, с учетом того, что оно происходит на си и содержит синтаксический шум. Нужно чтобы писать развесистые ооп программы, например, интерфейсные.

Представляется естественным писать на яп, имеющим большую историю промышленной разработки. На ооп языке удобней писать реально большие объекты. В частности, есть такой здоровенный инструментарий, под названием

  • boost. Это очередной большой комбайн, на этот раз для C++. Что умеет? Алгоритмы. Хитрые заранее заданные. Сразу видно, что библиотека уровнем выше. Туда же относится подсистема работы с графами. Поддержка различных компиляторов. Довольно важно, когда вы имеете дело с каким-нибудь вижуал це. Модульное тестирование есть и в глибе, и в бусте. Функциональное программирование, функторы. Переписана работа с памятью, ввод-вывод, обработка строк, синтаксический и лексический анализатор, свои структуры данных. Переписаны указатели — есть отдельный блок — умные указатели, которые обеспечивают безопасную работу с памятью в C++.
  • Qt.
    • базвый модуль — переписаны основные функции, введено понятие приложения.
    • виджетсет — самая известная часть Qt.
    • QtNetwork. Работа с сетью, всякие прикладные протоколы, сервисы которые слушаают порты из коробки, бац объект, шлеп объект, оно работает.

    • OpenGL, работа с графикой
    • SQL, интерфейс к SQL’ным базам данных, разным.
    • поддержка разных форматов — svg, xml
    • test — библиотека для тестирования.
    • некоторое количество привязанных к подзадачам модулей. можете броузер встроить. Организация просмотра документации, проигрывание мультимедии, полнотекстовый поиск, активикс элементы, поддержка скриптового языка встроенная, декларативные языки можно самому создавать, чтобы интерфейс описывать и всякое такое. Всякие странно себя ведущие виджеты, которые не умеет ОС (например, полупрозрачная лягушка).

Кутэ призвано сделать так, чтобы разработчик мог не смотреть на ос. Разработчики кутэ распылились на решение конкретных задач, которые выпдают на долю промышленного программиста при непростом программировании. Если надо решать реальные задачи, то вы берете кутэ и решаете их достаточно быстро.

Главная фишка кутэ — это вообще не C++. У кутэ объектов динамическая типизация и много всего ещё. Вы пишите программу якобы на с++, а потом натравливаете специальные компилятор, который учитывает мета-штуки. С точки зрения профессионала которому надо быстро закодить специальную функциональность — очень круто.

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

Ешё один пункт Java, .NET — у них всё в себе. Помимо языка программирования такие штуки содержат ещё и все остальное. Когда пишете на Java — ничего не надо знать про ОС, да и цикл разработки полностью поддержан. То же самое относится к любым современным скриптовым языкам общего назначения, особо это очевидно для питтона. В базовой поставке это несколько сотен модулей. Плюс привязки к библиотекам pyqt, pygtk.

Последняя часть — помимо комбайнов, существуют дисциплины, среды программирования. Ruby-on-rails. "Философии программирования в вики". Можно регламенировать всё, что угодно, вплоть до ограничений на внешний вид мозга программиста. Или, например, есть громадная область — дизайн паттерны — давайте программировать только готовыми паттернами. Есть целая дисциплина программирования, которая повелевает программировать только паттернами, и тогда не бывает коредампов.

Лекция 9. Перевод и интернационализация

Если продукт большой и предназначен для разных языковых сред, то вопрос перевода становится актуальным. В прошлый раз Андрей Черепанов её прекрасно прочел -- координатор перевода кде, но на этот раз он сказал, что изменил свое мнение и перевод этим людям не нужен. Лектор из соображений попробовать все переводит несколько проектов, в частности блюфиш.

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

Зачем может понадобится вносить исправления в исходный код при адоптации к культурно-языковой среде. Перевод это только одна из подзадач. Дело в том, чтобы с получившимся продуктом стало работать удобнее. Это наша основаная идея -- вводить свойства не что бы они были, а чтобы пользовательские задачи лучше решались.

Перечислим задачи, возникающие в процессе адоптации.

  1. Текстовый перевод. Причины:
    1. Человек так устроен, что больщая часть гарантированного взаимодействия между ним и компьютером происходит через текст. Текст формализован, его можно вывалить много.
    2. Программы изначально писались рассчитывая на ввод и вывод текста. В линуксе целый класс продуктов, которые только этим и занимаются.
    3. Перевод это ресурсоемкий процесс. Но именно перевод сообщений программы, особенно просто новой версии программы может быть значительно проще.
    Встает вопрос. Вот у нас есть большая годная программа, в ней много интерфейсных штучек. Все ли кнопочки и менющечки надо переводить? Неизвестно. Можно вообразить ситуацию, когда текстовая строка не нуждается в переводе. Например, перевод на русский язык функций в экселе. Существуют сущности, перевод которых не требуется. Задача перевода осложняется выбором что переводить, а что нет.
  2. Планирование интерфейса и дизайн. Типичный пример -- языки с другим направлением письма. Типичный пример -- радиобаттон. При направлении райт-ту-лефт кнопочки должны быть справа. Ещё проблема -- размер текста может увеличиваться. Английский русский - 100 - 130. В руководстве по локализации для винапи написано -- делайте диалог такого размера, чтобы туда влез любой перевод, если переводы планируются. В дотнете это тоже не поправили. Так что иногда и такие глупости приходится учитывать
  3. Локаль надо переводить. Локаль это сборник культурных параметров -- даты, денежные единицы, единицы измерения. Всем известен формат представления таблицы цсв. Везде разделяются запятыми, а в русском точкой с запятой, потому что запятыми отделяется дробная часть числа.
  4. Звуки. И вообше всякая мультимедия.
  5. Картинки.
    1. На картинках может встречаться текст.
    2. На картинках могут встречаться культурные реалии страны изготовителя. Например, полуцилиндрическое красное ведро -- американский почтовый ящик. Или, например, ислам запрещает изображение людей и, кажется, даже животных. И так далее. Культурная аттрибуция очень много значит.
    3. Сочетание клавиш. Ctrl-X, Ctrl-Z. Нажимаете, а это Ctrl-Ч. Или редактируете текст в хтмле, а обозначение болда. Переводить или не переводить? Уж не говоря о иероглифическом письме. "Для выхода из программы наберите иероглиф недостаточно просвещённый человек"
    4. Шрифты. Со шрифтами три года назад было печально -- люди помнили, что такое шрифтовой дизайн, сколько на это надо времени, и не брались. Дизайн шрифта это примерно 3 человеко-года. К 2005 году правильных векторных шрифтов практически не было. Но потом сначала редхат начала делать либерейшн, потом появилось и развилось джву. Поэтому проблема не стоит так сильно, за несколькими исключениями
      1. Если вы хотите делать локализацию на язык, для которого нет начертания.
      2. Если вы пишите кроссплатформенное приложение -- не вздумайте измерять ширину чего либо в знакоместах, если шрифт не моноширный. Длина виджета, измеренная в количестве букв это издевательство над виджетом. Вы не сможете это проверить, если на целевом компьютере будет подстановка шрифтов.
      3. Ссылки на внешние ресурсы -- телефоны, урлы. "Приезжайте в Маунтайн-Вью, всё сделаем".

Итого, задачи на 90 процентов не разработческие. Технических задач всего три

  1. Унифицированность по локализациям
  2. Программная адаптация -- радиобаттоны ртл.
  3. Сделать удобства для разработки многоязыковой программы. Чтобы не надо было руками сообщения перевбивать и так далее.

Сопутствуюшие вещи из реального мира -- коробки, книжки, лицензия.

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

Лектор ещё помнит времена доса, когда был такой термин руссификация. Люди брали программы и заменяли английские буквы на русские. Удл вмето Del.

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

Попробуем по другому. Вместо "вывести строчку "Введите число"" написать "выведите ресурс 34583". В коде вообще не встречается диагностика на разумном языке, а только обращения к подсистеме локализации. Маразм удобен тем, что если мы надрессировали программистов, они вообще не думают над сообщениями и не занимаются литературным трудом. Зато когда вы читаете код, вы вообще не понимаете что там происходит.

Разбиваем задачу на две.

I18n -- internationalization

  1. Globalization g11n
  2. Localization l10n

Что из этого списка можно более менее оптимизировать.

Взяли и провели глобализацию -- для каждого ресурса вместо обращения напрямую сделали косвенное. Оптимизировать можно текстовый перевод. С текстовым переводом есть несколько забавных клауз.

  1. Если у вас есть книжка, и автор выпустил новое издание, то если автор много переписал, то переводчику много переводить. А вот что касается текстовых идентификатров внутри программы -- ситуация может быть лучше. Вряд ли вы переназвали все кнопки внутри программы. Вы могли переставить их местами, или удалить две вставить три, или некоторые переназвать. В любом случае, если есть готовый перевод, то обновление может потребовать меньше работы, чем обновление, скажем, перевода документации. Правда возникают соображения -- все ли текстовые сообшения надо переводить. Мало ли для чего может понадобится const char *. В постфиксе вон в начале каждой функции стоит её описание в строке. Как в питоне докстринг. Типичный пример. "Найдено 18 файлов". Какие проблемы? %d файл, файла, файлов. Словоформ и правил их образования разные количества. Это достаточно легко автоматизируется, но это надо предусмотреть.
  2. Достаточно легко оптимизировать определение размера виджетов, если об этом задуматься на уровне упаковки виджетов. Если библиотека виджетов задумывается о том, что ширина текстовой метки может быть разной. Но библиотека должна ументь размещать виджеты так, чтобы они друг на друга не наезжали. Gtk и QT это умеют. Можно конечно так надизайнить интерфейс, чтобы было неудобно, но во многих случаях задача решаема.
  3. Локаль. Библиотека, которая умеет выводить данные с текущей локалью.
  4. Полуавтоматический вывод картинок.

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

Когда в 2009-2010 догромыхивало внедрение линукса в школах, деньги разворовали, ничего не внедрили -- вышел дистрибутив, который предполагался обновлением предыдущего пспо -- пспо5. Этот самый пспо5, там было проделано много работы, Inkskape был заменен на Редактор Векторной Графики. И кроме того был переведен учебник по geany. Причем перевод был написан на хтмле. Печальная история.

Непрограммные проблемы. Поведение переводчиков. Перевод это не древнейшая, но древняя профессия - толмач. С серьезными традициями, в том числе и технических. Например серьезный переводчик всегда работает с translation manner??. Сборник уже переведенных предложений. Особенно важно когда ижет промышленный перевод технической документации. Там много текста с малым количеством параметров. К сожалению, страшно далеки они от народа и никто не думае при заточке перевода продукта о реальных переводчиках. Сейчас эта тенденция потихоньку меняется, но и интерес к переводу ослабевает. Информационная связность провоцирует единое информационное пространство с единым языком -- современной латынью, то есть упрощенным английским. Мотивация ослабевает, это видно по тому, какая команда переводила кде3 и какая кде4.

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

Хорошо ещё, если множества слов не пересекаются. Паттерны-шаблоны-темплейты.

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

Чтобы хорошо переводить внутренности программных продуктов, человек должен хорошо знать и пп, и русский. А таких мало. Поэтому как правило есть координатор, который знает и то, и то. И набор людей, которые знают что-то одно.

Пора переходить к примеру инструментария по переводу.

gettext

Нету падежных форм. С ними вообще все плохо.

Главный принцип -- написана неглобализованная программа. В ней есть строки. Строки являются идентификаторами второго уровня вложенности. \

Бонусы:

  1. Геттекстизация состоит в расставлении оберток.
  2. Вы можете взять и одним двидением руки с помощью xgettext вынуть все строки для перевода.
  3. Если в вашей программе внезапно выяснилось, что строки в разных местах содержат одинаковые значения, то вы это быстро обнаружите и сможете решить хорошо это или нет, а также дать единообразный перевод. Например, пункт меню "отфигачить".

Поддерживаются числовые словоформы в виде правил.

Fuzzy translation

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

Msmegre.

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

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

Qt linguist

gtranslator

Морды, которые представляют собою текстовый редактор по сообщениям с привязкой к исходным текстам.

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

omega t -- инструмент для переводчиков. Традициионнно пользуются виндовом trans. Омега т хранит в стандартном формате, которым, правда никто не пользуется.

Лекция 10. Linux-specific

Сегодня вместо Георгия Владимироваича Евгений Леонидович.

Тема сегодняшней лекции программирование в линукс.

Linux-specific.

Или за что вас будут не любить те, кто будет портировать ваш код на другие ос.

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

Помимо декларированных в позикс есть ряд линукс-специфичных.

Итак, первое линукс-специфик

  • API
  • Следующее — набор виртуальных файловых систем. fs interface
  • Третьим аспектом является ряд иных специфичных для Линукс вещей, всякая несистемная да.

API

Значительно пересекается с POSIX. В попытке реализовать POSIX-совместимое API становится понятно, что в реальной жизни всё не так хорошо. Есть ряд вещей, которых нет в POSIX’е, но хорошо бы иметь. Что сюда входит

  • POSIX extensions. добавляем дополнительных флагов, всякие системные вызовы, согласуюся с позикс апи или просто не были включены в стандарт. Например, 64-битные офсеты в файлах. Например, когда стало понятно что файлы бывают больше 4 гигабайт, а ос были ещё 32. Понятно что это ксается только 32битных платформ. В начале было решени использовать набор спец вызовов с суффиксом 64. Потом перешли к более ормальному решению, которое заключалось в изменение типа оффсета.
  • Linux extensions. За время разработки ядра, за более чем 30 лет, туда было включено много овещей, которыее делают жизнь удобней вне зависимости от стандартов. Поллинг epoll, посылка в сокет нескольких сообщений за раз. Всякие возможножности поддержки тредов, proc namespace и капабилити.
  • среднее между двумя. Например доп возможности касающиеся управляения памятью -- вы говорите какой тип доступа к какому куску памяти будет чтобы лучше её кэшировать. Или, например madvise(). В очередной редакции позикса он появился, но был и до этого, и в линуксе немного отличался,линукс допускал ряд дополнительных флоагов и им вы могли давать хинты, которые ядро пправда воспринимало как команды, но это неважно. Суть в том что флагов было больше и они были линукс специфичны.
  • костыли. obscure features. Хотите сбросить кэш на платформе мипс, говорите кэш флаш, и на мипсе аппаратно сбрасывается кеш. Или vm86 переход в виртуальный режим 8086. Или поддержка архитектру целл выливается в два системных вызова ... системный вызов айдл, который говорит процессцу 0 чтобы он удалился из себя, потому что он уже породил инит 1 и больше не нужен. Туда же относится управление ктрл-альт-дел.

POSIX&Linux extensions

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

Начнем с модели управления процесса и управления памятью. Это довольно важно.

Управление процессами
  • Дистниктив фичерс.
  • Поддержка тредов
  • поддержка non uniform memory architecture
  • поддержка капабилитис
  • process namespaces
  • process accaunting

СЕЛинукс? Раньше для него приходилось хачить ядро, теперь есть ядерные интерфейся для добавления секьюрити модуля, который добавляет секьюрити проверки соответственно собственному разумению, эти модули могут быть разными. Апарм, СЕЛинукс.

В чем идея с тредами. Для тредов есть local storage, чтобы у каждого было свое личное адресное пространство -- это может быть реализовано как компилятроом, так и на уровне ядра. влинуксе есть на уровне ядра. У вас есть дополнительные системные вызовы. gettid(), get/set_thread_area(), ну и, соответственно, tkill(). Чтобы эта реализация была более менее прозрачной, у вас есть главный тред и тред_ид главного треда совпдаает с процесс_ид. Если вы не думаете о тредах, у вас все прозрачно, иначе надо использовать дополнительные вызовы. Ими же пользуется библиотека pthreads. Есть ли смысл пользоваться напрямую ядерными вызовами? tkill вы не сможете сделать напрямую. Есть ряд системных вызовов которыми надо указывать тред_ид, а без системного вызова его не получить. птредс вещь внутри себя, внешней штукой ими управлять не получится. птредс они про другое, про многопоточное програмиирование, а системных вызовов полторы штуки. В птредс большинство вызовов именно тред_авар, а не для работы с собственно тредами. Ещё сисвызовы для тредов tgkill(), exit_group(). Как создать тред? clone(). Или clone2(). Есть такая мода давать цифровые суффиксы всяким модным вызовам. epoll_create epoll_create1 вызовы более крутые в том плане, что у них поменялся ап/би??, то есть не апби ааа.

  • getcpu()
  • migrate_pages()
  • move_pages() -- перемещает не все страницы, а только указанные в другой полл.
  • get/set_mempolicy()
  • shed_setaffinity() высатвляет аффинити для тредов.

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

Есть возможность привязать процесс не к ядру? Сетаффинити. А сет_мем полиси позволяет указать мемори пулл, который надо использовать данному процессу.

капабилити это ряд прав/возможнстей, позволяющих выполнять различные системные вещи -- монтирование, сет_уид, итд. Капабилитей много. как рут так и не рут могут вполне себе ими обладать или не обладать. С одной стороны можете получить возможность сделать рута распредленным. Такое было впервые реализовано в солярис. Из соляриса перетащили много секьюити фишек - пам, капабилити, в каком-то плане контейнеры и процесс аккаунтинг. тем не менее сейчас влинуксе это все есть, капабилити появились достаточно давно,Ю кажется в 2.2. Управлять темикакпабилити которые вам доступны можно через prctl(). Там можно получить и задать маску доступных, можно задать маску капабилитей которые дети не получат, и так далее. когда вы планируете делать клон вы можете уаказать что наследует дочерний процесс в том числе и касательно капабилетей. Кстати, посредством клон сейчас реализуется форк, потому что он более круче. capget(), capset() позволяют указывать непосрдественные капабилити тредов. Да.

proc namespaces. Иногда хочется изолировать процессы от окружающего мира тем или иным образом. Изолировать хочется по разному, например дать и мдругой рут, например не показывать им другие процессы, не показыавть им какую-нибудь информацию о системе касающуюся памяти и процессора и так далее. Неймспейсы позволяют это делать. Вы можете породить новый неймспейс у процесса и сказать, что там новая жизнь. Он будет не в курсее про другие процессы и не сможет общаться с другими процессами даже посрдеством сигналов, хоть и будет от того же пользователя. вызов seths(). Gjxtve pfujdjhbkb ghj fengjqyn? gjnjve xnj [jntkb egjvzynm ghj зшмще_кщще и у какого то вызова , маунта или нет, есть возможность указывать какие маунты видит процесс и все его дети. Кажется это делается prctl().

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

Работа с файлами

Там гораздо больше всякого интересного. Файловый интерфейс этоосновной способ абстракции любого ввода-вывода. Как следствие этот интерфейс функуионально довольно богат и там наиболее интересные механизмы которые интересны и с тз юзерспейса. С расширениями касающимися работы с файлами вы столкнетесь почти наверняка.

  • epoll. Для того чтобы обслуживать большое колво файловых дескрипторов, сетевые соединения или пайпы есть примерно два сисвызова -- селект и полл. работают они не лучшим образом если вы хотите следить за сотнями или тысячами дескрипторов. м надо указывать каждый раз за чем следить, то есть пересоздавать указывающую это структуру. Подход еполл заключается в том, что вы создаете некий ядерный объект, посредством специального интерфейса — eploo_create? epoll_create1, дальше посредством epoll_ctl можете понавешать в это т объект фд за которыми хотите следить, и для них указывать виды событий, которые хотите получать. Можете следить за тем что там произошли чтение, запись, закрыти едлескриптора. Перфикс довольно очевидные epoll_ctl(epfd, EPOLL_CTL_ADD, fd, event). В ивенте собственно описываете за каим событием следить. ивент имеет тип struct epoll_ecevnt у него есть поле events == EPOLL_IN|EPOLL_OUT). Событие может происходить по событию чтение/записи или по наличию данных edge triggersю Для дескриптора специального вида вы можете повесит еполл_евент на фд который соответсвует другому еполл_евента или фд который соответсвтует дескриптору для сигнала. или еще что — много всяких специальных дескрипторов и когда с ними что-то происхходит они будут считать что с ним что-то произошло, но вас интересует левел. Еще epoll one shot позволяет евент для однократного срабатывания. После того как вы еполл цтл подобавляли всяких ивентов вы можете делать epoll_wait epoll_pwait. Префикс P. Если вы делаете пселектесть проблема. Когда вам надо замаскировать сигналы и посавить селект, потому что вы хотите во время селекта, полла, вейт получать какие то сигналы. возникает рейс кодишн маску поменяли, а вейт не поставили. Наборы специальных вызовов с префиксом p ppoll, pwait, pselect -- дополнительный параметр маски сигналов, вместо лвух вызовов получается один. Если у вас левел-триггеред обработка, то вы можете безболезненно заменить poll на epoll и все будет работать.

Есть ещё одна задача, которая касается слежения за всякими событими в системе. Было два механизма dnotify и inotify. dnotify делался через fctrl вы могли взять дескриптор соотв директории и могли передать дескриптор директории для ..

На смену dnotify примерно во времена ядра 2.5. пришел inotify. В маске вы указываете много клёвых параметров, за которыми вы хотите наблюдать.

infd = inotify_init() wd = inotify_add_watch(infd, path, mask)

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

Делается просто рид и читаете вы оттуда структуру соответствующего вида. структура Notify_event в которой указан вотч_дескриптор, указано чего случилось посредством аналогичной маски.

Когда говорили про управление памятью там еще один аспект возможно стоит подчеркнуть. Маппинги довольно смещно осуществлялись одно время. В связи с необходимостью 64 битных оффсетов. Помимо мап в ядре сейчас определён мап2, которые принимает смещение по страницам. Единственная архитектруа которая в 32 битном режиме может адресов более чем 32-битные адреса это х86 PAE, но в люом случае есть мап64 у которой 2 параметра типа старший бит и младший бит.

По поводу памяти ещё есть возможность ремапить всякие вещи. map из позикса, а вот remap нет и без него страдали.

Ещё можно делать нелинейный мапинг.

sys_info позволяет много чего получить.

Большинство из этого неактально для юзерспейс программистов.

Во Фре есть kqueue который очень крутой и умеет тоже самое что epoll, inotify и не только.

Юзерспейс программисты обычно просто берут и используют libevent.

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

Файловые системы специалных функций довольного много. Самые известные procfs. Впервые она появилась в солярисе или бзд, она позволяла и позволяет узнавать информацию о процессах. Линукс отличается тем что прокфс есть кроме информации о процессах много чего. Интерфейс прокфс довольно простой. Многие ядерные подсистемы экспрортируют туда информацию о себе и позволяют собой управлять. Когда создается там файл создается структура с коллбэками на чтение и на запись.

/proc/sys

/proc/sys/ возможность воздействия на ядро — лимиты, особенности поведения. То же самое делается посредством сисвызова sysctl(). Сисктл доступно довольно много чего. net, kernel, vm, fs. Помимо этого в самом прокфсе ещё... вот.

Гораздо интересней другая специальная фс.

sysfs. Это способ просмотра иерархии kobject это часть объектной модели ядра Линукс. Нельзя пафосно сказать, что она там есть, но там можно создавать объекты и предоставлять к ним доступ на чтение и запись и механизм событий. Из юзерспейса возможны колбэки на чтение запись в sysfs. Когда вы в sysfs добавляете файл, вы добьавляете объект в определённую подсистему, а там уже предоставляются аттрибуты для доступа к объектам. Сам кобжект это директория а аттрибуты это уже регулярные файлы.

Может кто-то не в курсе, как организуются подобные штуки в ядре.

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

struct my_obj{

struct kobjext kobj;}

container_of(obj, struct my_obj, kobj)

Далее после того как вы создали структуру вы можете делать kobject_init для инициализации этого поля внутри структуры. После вы можете делать kobject_add и на этом этапе вы должны указать ktyoe которая должна быть проассоцирована с этим объектом. Это надо затем что а, да. Структуру ктайп вы указывате ещё на этапе инициализации. Когда вы добавляете вы указываете родителя данного объекта. У кобжекта есть имя. Оно тоже определяется посредством API. . Это рассказывается чтобы вы сбее представляли как устроен сисфс.

Как устроен рефкаунтинг? Когда вы хотите поработать с кобжектом, вы сначала делеает kobject_get, потом kobject_put. Если рефкаунтер дропнулся до нуля, т ообъект удаляется. рефкаунтеры инкрементятся на каждый адд у парента, но если вы сделали циклическую зависимость, то вы перед кобжект релиз должны сделать кобжект дел.

Чтоюбы получать доступ к аттрибутам сисфс есть вызовы sysfs_create_file и sysfs_create_group — первое позволяет создавать кобжекты. второе каталоги их группирующие. Сисфс гораздо более внятно организована сама по себе когнитивных свойств не несет, а просто предоставляет доступ к иерархии кобжектов, но на ней надложено дополнительное полиси как должны себе вести какие модули. Модули ядра которые работают с пси, итд соблюдают неокторые правила и генерируют все красиво. Туда перетащили доступ к платформе, к ацпи. Все мигрирует из прока в сисфс, потому что уже ненужно например уеликом реализовать семантику чтения и доступа к файлам итп. Там еще подсистема наворчивает свои коллбэки.

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

у селинукс есть своя фс, которой он управляется и у cgroups тоже.

Костыли-костыли. Если кто не знает read ahead, который позволяет сказать закэшируй кусок файла я его сейчас прочитаю это тоже тркщтр

Поскольку с Линуксом ассоциировано некоторое окружение, то пользуясь расширениями этого окружения вы впадаете в линукс специфик.

glibc,gnu lib c , мантейнтится в районе редхата.Это двольно развесистая и тяжелая библиотека со своими особенностями. Там появились библиотечные вызовы с суффикосм с для некоторых вещей у которых не было нормального о. Помните наверное фгетс у которого не было нормальной обработки баффероверфлоу, таких вызово намного больше, которым хорошо было бы передавать количество байт.

coreutils также имеют расширений вагон и маленькую тележку потому что то, что специфицировано в POSIX, совсем грустно. Берёте, открываете любой мануал по coreutils и находите у команд по две трети GNU-специфичных опций. xargs

В Линуксе поддерживают расгиренные аттрибуты и есть специальное апи по их управлению. они умеют указывать что файл имеет соотв капабилити.

shell(bash). некоторые сичтают что програмиирование на баше это программирование на шелле. Это не так. Баш не стандартизован, набор возможностей меняется с каждой версией. и даже если говрите #!/bin/bash вы моложец что не сказали бин ш, но непонятно какой это бэш первый,второй, третий.

Башизмы

  • pipestatus
  • [[]] , но не $(()), но если вы пишите if (()) надеясь таким способом заиметь результат арифм вычислений для ифа вы будете страдать, потому что это расширение
  • си стайл циклы
  • variable expansions типа ${ / / } ${ : : } $RANDOM

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

  • FUNCNAME
  • <<<

  • < () > )

  • управление стдин стдерр &> > 2>&1

  • shopt
  • read опции вайл после пай
    • $(cat file|while read do done)
  • local

Далеко не всё, чем богат Линукс, здесь рассказано. xattr, и ещё много, что лектор вспомнил, но забыл. Среди костылей есть много костылей, без которых жить очень грустно.

Лекция 11. Информационное-технологическое обеспечение разработки

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

// О! Я нашёл нормальный, вкусный мел! Ну ладно...

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

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

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

Чего мне не хотелось бы касаться в этой лекции:

  • Всякие низкоуровневые инструменты.

  • Средства ОС. Довольно большая часть той информационной составляющей, которой вы будете пользоваться, до сих пор довольно хорошо обеспечивается самой операционной системой: маны, инфо, etc.

  • Средства DE. Другая крайность, о которой тоже подробно говорить не получится — это информационные особенности конкретных средств разработки. Ну не буду же я сейчас рассказывать, как надо работать в Эклипсе!

Анекдот: Я не поехал читать лекции в Моторолу по Embedded Linux. 3 месяца подряд они не могли понять, что им не подходит график. И все эти 3-4 месяца, пока они искали тренера по Линуксу, у них не было ни одного человека, который понимает, что такое Линукс. И при этом они выпустили несколько телефонов на Линуксе.

Это я привёл пример неправильной организации труда.

В Windows поддержка разработки со стороны ОС всегда была никакая, а понятие комьюнити — оно совсем другое, когда речь идёт о закрытом софте.

Информационное пространство

О чём вам может потребоваться информация, когда вы занимаетесь программированием?

  1. ЯП. Ну разумеется о языке программирования.

  2. Инструментарии. Это громадный набор всяких инструментов и немыслимо программировать на том же Qt без годной справочной системы.

  3. ОС. Даже если вы делаете свой проект совсем в отрыве от ОС, что-нибудь кроссплатформенное, вам как минимум потребуется информация об ОС, чтобы не запрогать что-нибудь _не_кроссплатформенное.

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

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

Где искать?

  • Документация

    • поставляемая с ЯП/инструментарием
    • встроенная в IDE
  • Google
    Где-где — в Гугле! Почему я с такой уверенностью тыкаю на Гугл? У него есть 3 ценных свойства:

    1. Будучи по своей сути изначально гиковским проектом, Гугл до сих пор сохраняет за собой способность больше других систем ковыряться в программистских информационных помойках. Есть такой замечательный совет: Если в вашей программе в самом неожиданном месте вылезла непонятная ошибка — скопируйте и загуглите её целиком. Весьма вероятно, что вы найдёте самое подробное описание вашей проблемы и её решения.
    2. Гугл следит за вами. Большой брат. Внимательно, подробно!.. Это, конечно, плохо, но зато, если вы, скажем, на работе, то он будет выдавать одни результаты на ваш запрос, а когда вы дома — другие результаты. Это иногда зверски помогает, когда вы вводите размытый вопрос. Можно ещё и залогиниться! Тогда он будет лично на вас стучать! Я пока не боюсь... ПОКА %)
    3. У Гугла у самого есть всякие программные наработки и то, что вы ищите, может вполне оказаться на ресурсах Гугла.
    • Наверное, Гугл — это не первое место, куда нужно ходить. Просто самое универсальное. Первое место, куда нужно ходить — это ман. Конечно, всё равно большинство сначало лезет в Гугл. Я сам грешу этим. Единственное моё оправдание, что Гугл лучше именно _ищет_.
  • Документация дистрибутивов
    Есть одна тонкость, которая часто спасала меня. Если вы ищете что-то специфическое, посмотрите не только родную документацию для инструмента, которым вы хотите воспользоваться, но и документацию, специфичную для вашего дистрибутива. Многие дистростроители имеют своё мнение по поводу того, как должен работать и где лежать используемый вами инструмент. И это мнение может не совпадать с мнением апстрима. readme.alt¸ readme.debian — это то, куда я довольно часто приземлялся в поиске ответов на нестандартные вопросы.

  • Списки рассылки

    • ЯП / инструментария
    • Дистрибутива
    Речь идёт о том, что сами дискуссии и сам движок поиска по ним представляют собой весьма полезный оффлайновый источник информации. То, что в гугле было на 80 странице, тут может быть на первой.
  • Сайт
    Не стоит сбрасывать со счетов и всякие базовые сайты по тем инструментам, с которыми вы работаете.

    • Оф-сайт (факушки всякие)
    • Сайт сообщества (comunity driven)
      Место, где сообщество подготавливает всякие советы по поводу того, как работать с вашим инструментарием.
      Что изменилось в новой версии и как с этим бороться? В документации boost’а написано, что появились новые фичи. А где искать информацию, почему моя программа, которая на нём работает, перестала собираться? Вот на таких вот сайтах сообщества.
      Вот, например, на сайте gcc написано «Мужики, не задавайте сюда вопросы». У нас есть замечательная рассылка. Там вопросы. А здесь ответы.

  • Участие в рассылках
    Да, вам будет приходить какое-то количество писем. Научитесь их быстро читать или проглядывать. Зато когда возникнет какой-то вопрос, вы будете по крайней мере знать, обсуждалась ли эта тема в рассылке.

И вот только если ничего не помогает, вообще-то говоря, не плохо бы спросить. У кого спросить?

В первый раз за сегодня я ссылаюсь на текст, прекрасно написанный Саймоном Г. Тетхемом. В чём идея? Если вы задаёте свой вопрос, имейте в виду, для чего вы это делаете. Есть две такие подспудные цели, которые есть у человека, который задаёт вопрос:

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

Что должно быть в правильно заданном вопросе?

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

Всем советую почитать этот документ. Речь идёт о том, что ваш вопрос могут решить в списках рассылки и других интерактивных ресурсах, если вы его достаточно правильно зададите.

  • IM/Forum/IRC/Jabber-конференция
    C давних пор принято у таких олдовых программистов тусоваться в IRC. Почему это не очень хорошее решение проблемы? Нет, сначала — почему это хорошее решение проблемы? Это онлайновый и быстрый способ.
    Почему плохое?

    1. Вы, возможно, не нарвётесь на того человека, который способен решить этот вопрос.
    2. Ваш вопрос останется только в логах конференции, и другому человеку с той же проблемой гугл уже не поможет.
    3. Есть классы пользователей (учителя) даже достаточно компьютерно-ориентированных, пользующихся только форумами.
    4. Смысловое наполнение ниже, а уровень информационного шума выше.

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

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

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

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

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

Есть два решения:

  • выкинуть и заменить на системное
  • форкнуть и сопровождать

Опенсорс в стиле 90-х годов прошлого века представляет собой следующую картину. Люди просто складывают все необходимые инструменты в одно место и начинают всё там переправлять, включая gcc. Особенно этим славятся американские разработчики, работающие на военку.

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

Здесь совмещаются две ваших роли: роль ваша как разработчика собственного проекта, так и роль ваша как разработчика того софта, который вы используете. Большие и серьёзные люди как правило являются участниками очень большого числа проектов. Такие разработчики являются участниками как минимум нескольких сообществ, участвуют в разработке своего дистрибутива и т. д.

Есть также вещи, которые вы не найдёте в интернете. Это всякие учебники и методички. Их так же нельзя сбрасывать со счетов. Интернет, конечно же, тоже полон всякими методическими пособиями. Только нужно хорошо представлять себе, что они могли быть написаны 10 лет назад и информация в них могла несколько устареть.

Ещё очень полезно брать и читать чужой код. Особенно тем, кого от этого не тошнит в духе «Ну что это такое? Как это можно читать? Этот человек писал ногами».

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

Разработка проекта

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

  • Хранилище кода

    • github
    • gitorius
    • bitbucket
    • launchpad
    • sourceforge
    Чем они хороши?
    1. Они удобны для индивидуальной разработки.
    2. Они хороши, когда у вас такая более или менее спонтанно собирающаяся команда. Команда есть, цель есть, но тратить время на то, чтобы как-то оптимизировать процесс разработки, у вас нет никакого желания. Такого рода сервисы предлагают вам готовые решения для совместной разработки.

Вторая опция состоит в том, чтобы развёртывать собственное хранилище кода.

Когда вас становится больше троих, внезапно выясняется, что у каждого должно быть своё хранилище кода и возможность работать с общим репозиторием. У вас должна быть некоторая услуга предоставления хранилища кода. Типичный пример такого инструментария — это gitalite. На нём ведётся разработка ядра. У gitalite'а есть ещё одна удобная особенность — он управляется гитом. Вот тот же гит, только администраторский. Помимо gitalite есть ещё gitlab — это такое поделие, представляющее собой gitalite + морда на RubyOnRails. Это уже модно, это всякие фишечки и плюшечки.

Это всего лишь примеры. На самом деле любой приличный CVS и DCVS имеют серверную составляющую и для них написано некоторое количество обвязок.

  • Информационный обмен
    Я сейчас говорю об информационном обмене в процессе разработки. Понятно, что для этого используются всё те же списки рассылки. Всё те же сайты, на которых откладывается стабильная информация. Активно используются всякие мессенджеры.

    • Mailman
    • EJabberd (он кошерный, на Ерланге написан...)

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

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

    • Bugzilla
    • RT
    • ORTS
    • Mantis
    • Gnu(s) (давно уже не используется)
    Самое главное свойство системы отслеживания ошибок состоит в том, что всё заканчивается неким одноразовым импровизированным списком рассылки. Информация доходит всем тем людям, которые мотивированы или профессионализиров реагировать на это изменение. Ещё одно важное свойство то, что это именно система _отслеживания_ ошибок. Главная идея состоит в том, что у ошибки есть определённый воркфлоу.

Наконец два последних пункта:

  1. Мы ничего не поговорили про управление разработкой проекта. Инструментов, которые управляют разработкой, бесконечно много. Это называется project management. Во-вторых, это дело уже не совсем программиста, а скорее менеджера. В-третьих, в зависимости от особенностей процесса разработки может быть выбран тот или иной инструмент. А в-четвёртых, я теряюсь..

  2. Комбайны. Они существуют разные. trac — это такая штука, в которой есть и вики, и система отслеживания ошибок, и система отслеживания процесса разработки, и ручки к VCS’у. Это такой комбайн, достаточно простой, который позволяет быстро развернуть процесс разработки, как с информационной поддержкой, так и с инструментальной. Ещё один пример: roundap. Другая группа комбайнов, которая вообще совмещает в себе всё и сразу. В них есть составляющая для работы с кодом, составляющая для работы с пользоваталями и т. д. Я знаком только с одним гнездом, свободным в этом плане. Это redmine и его форк, который называется chilyproject. Это здоровенные комбайны, обучение эффективному использованию которых — отдельная серьёзная

    задача. Она написана на RubyOnRails. Ещё она каким-то образом привязана к мозгам менеджеров. Ещё менеджеры любят Atlassian, но она несвободная.

LecturesCMC/LinuxApplicationDevelopment2012/Conspects (последним исправлял пользователь eSyr 2012-12-21 01:54:07)