Путь в десять тысяч ли начинается с первого шага.

Преамбула


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

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

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

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

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

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

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

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

Перечисления против определений


Так вот, возвращаемся к нашим баранам, то есть типам и Вы, возможно, удивитесь, но тут действительно есть о чем поговорить. Для начала откроем файл… и сразу же натолкнемся на объявление пользовательских типов, которые автор планирует активно использовать в библиотеке, и сразу же кое-что из написанного мне не нравится. В данном случае я говорю о введении логического типа. Это не ошибка в принятом значении этого слова, но это большая ошибка в рамках принятой мною парадигмы программирования. Я считаю, что компиляторы пишут неглупые люди, и не следует считать себя намного более продвинутым по части использования языка, нежели они. Классическая реализация с дефайнами логических констант и использованием типа char приведена в данном файле, я противопоставлю ей реализацию через перечислимый тип, а именно:

typedef enum {False=0,True=1} Boolean;
Boolean IsOk=True;
...;
if (IsOk==False) ...;

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

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

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

Следующий плюс — компилятор Вам сам подберет необходимый примитивный тип для реализации, и перечислимые типы совершенно необязательно будут храниться в виде целого, если для хранения значения достаточно 8 бит. У меня IAR именно так и сделал, разместив переменные моего логического типа (типа перечисления) в байте. Вообще говоря, если уж мы хотим сами определить наш логический тип напрямую, то следует использовать не тип char, а at_least8_t или же fast8_t, а последние два никак не обязаны с байтом совпадать, хотя и могут. Более того, я не исключаю реализации компилятора, которые способны разместить логическую переменную в более подходящих для них местах, например, адресуемых битах в памяти, что несомненно эффективнее и по памяти и по быстродействию, чем самодельное определение.

Явные сравнения


Обратим внимание на строку, в которой написан оператор сравнения, и я категорически настаиваю именно на варианте с явно написанным сравнением в противовес с устоявшейся практикой «Что есть истина? — Не ноль», поскольку этот вариант явно указывает на требуемое условие, и не оставляет места для домыслов. Ну а если мы учтем, что 99% современных компиляторов (я бы написал ''все", но мало ли чего не бывает) увидев сравнение с нулем, не будут генерировать код для его осуществления (вернее, будет, но ровно такой же, как и для отсутствующего сравнения в в условии if (!error), то по эффективности мы не проигрываем. Ну и в завершение еще одно соображение — избегать переменных с именами типа NoErr (надеюсь, то, что наименование переменной должно совпадать со смыслом хранящегося ней значения, у Вас сомнений не вызывает, иначе у нас большие проблемы), поскольку возникают возможности разночтения и недопонимания выражений типа if (NoErr==False), а их следует избегать любыми способами и переменная IsOk в этом смысле предпочтительнее.

Также желательно писать условия в форме утверждения, то есть (IsOk == False) предпочтительнее, нежели (IsOk |= True), при этом вариант с ~(IsOk == True) даже не должен приходить в голову. Хотя есть люди, которые утверждают (хотя, может, это был троллинг, но для меня слишком тонко, есть вещи, шутить о которых неуместно), что вариант State|= !!ErrNumber << 5 эффективнее реализуется, нежели нормальное выражение if (ErrNumber! = 0) State = State | (1 << StateErrorBitNumber). Даже если бы это была правда, а на моем компиляторе код не получился ни короче, ни быстрее, то эта эффективность не стоила даже минутного ступора от подобной языковой конструкции.

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

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

БИТОВЫЕ ПОЛЯ


Кстати, по поводу битовых полей. Те, кто читал мои опусы, знают, а для остальных повторю еще раз — если Вы можете контролировать порядок байт в слове в своем компиляторе, то битовые поля являются поистине восхитительным способом работы с отдельными битами и битовыми полями как в регистрах, так и в обычных переменных и я настоятельно рекомендую такой возможностью пользоваться, поскольку выражение if (ErrNumber! = 0) State.ErrorBit = 1 еще более понятно, чем приведенное в предыдущем параграфе. Если же религиозные соображения либо психологическая травма, полученная в детстве, когда Вы неожиданно открыли файл, написанный Вашими родителями и увидели там этот неприкрытый прием, не позволяют Вам применять битовые поля и Вы предпочитаете битовые маски, то у меня есть настоятельная рекомендация — прячьте операции с ними за макросами и определяйте зависимые константы одну через другую, а не индивидуально.

В своих ранних постах я данный вопрос рассматривал, в комментариях даже есть макрос, позволяющий наряду с традиционным определением связанных констант типа #define StateBitNumber (12)
#define StateBitMask (1 << StateBitNumber)
использовать и обратное в стиле #define StateBitMask (0x00004000)
#define StateBitNumber (BITNUMBER(StateBitMask))
. Постарайтесь придумать сами реализацию последнего макроса, а можете и посмотреть. И, ради Бога, используйте только производные от этих констант, свое отношение к людям, позволяющим себе выражения типа *(uint *) 0x20008000 = *(uint*) 0x20008000 & 0xffff0fff | 0x00004000, причем подобное выражения с одинаковыми константами встречаются у них в тексте многократно, я уже выражал, помните о больших кухонных ножах.

Обратите внимание, что битовое поле в регистре — это ни в коем случае не логический тип, а целый, пусть и длиной в 1 бит, никогда не делайте его тип отличным от целого, а вот в слове состояния процесса (в переменной) вы вполне можете себе позволить и такую конструкцию — логический тип битовой длиной 1. И не забывайте о ветви else, которую я вполне сознательно упустил в своем примере, чтобы он полностью соответствовал критикуемому оператору, который может быть и правильным в соответствующем контексте. В то же время, я не ограничиваю Вас в праве написать что- то вроде ProcessOK = (Boolean) (((ReadOk==True) || (WriteOK==True)) && (ErrNum ==0)), поскольку в данном операторе явно выражено Ваше понимание происходящего и декларирована решимость взять на себя ответственность за возможные последствия.

Имена переменных


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

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

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

Да, мы не сможем теперь написать очаровательное выражение типа if (~NoErr || !NoSuccess && NoRetry) ..., которое, несомненно доставит несколько малоприятных минут даже автору после полугодового перерыва, а про остальных даже и нечего говорить, но, поверьте, потеря не столь велика. А для выражения логических условий в понятной всем форме наш логический тип вполне приемлем, например if (IsOk==True) || ((Success==True) && (Rerty==False))) ..., где все просто и понятно, и даже если данная конструкция будет исполняться на 2 микросекунды дольше, в чем лично я не уверен, то оно того стоит. Как сказал кто-то из неглупых людей, мы пишем программы не для компилятора, а для других людей.

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

Выражайтесь ясно


Поэтому я настоятельно призываю Вас писать код насколько можно проще (известное правило KISS), которое постоянно нарушается, иногда из ложно понятого чувства профессиональной гордости (существует мнение, что чаще всего последняя запись на черном ящике звучит «смотри, как я умею»), но чаще просто по привычке. Читаю неплохую книгу «С 21го века», которую мне порекомендовали в комментариях к предыдущему посту, и вижу весьма привычное место static int Data=0; if (!Data) ...; и возникает у меня вопрос, с какой целью простое и понятное условие (Data==0) записано в таком виде, если мы решили по-извращаться, можно было бы написать и (~Data & 1) (данное выражение еще прикольнее и демонстрирует Ваше глубокое понимание поведения целых типов, а ведь это главная цель написания программы — блеснуть своими знаниями), а можно придумать и еще что-нибудь. Неужели это для того, чтобы экономить два символа при вводе текста а также место на диске при хранении файла? Конечно, все три варианта сработают идентично, и целый тип можно в С интерпретировать как условие, но, все-таки, зачем? Проверить знание читателем (пользователем) правил синтаксиса С — не надо, еще раз, у нас не собеседование.

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

Машинонезависимые типы


Идем далее по тексту программы и натыкаемся на очередное загадочное место, а именно на определения типов UCHAR и UINT. Почему загадочное место? Во-первых, непонятно, зачем эти типы вообще вводить, из их названия ровным счетом ничего не следует (мы с Вами договорились о значащих идентификаторах, воут?), во-вторых, из текста видно, что это просто алиасы для стандартных типов, а в-третьих, почему взяты стандартные типы, а не рекомендованные для применения во встроенном программировании типы с явным указанием размера. Неужели это всего лишь попытка набирать меньше символов при вводе текста?

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

Кстати, интересный вопрос для пытливого читателя — а как именно определены эти типы с явным указанием размера в реализации языка? Предлагаю на минуту задуматься, предложить варианты, а потом посмотреть код файла stdint.h в Вашем компиляторе. Лично я в своем IAR обнаружил алиасы для выражений вроде __INT8_T__, которые дальнейшему анализу не поддаются и, очевидно, должны быть реализованы собственно в компиляторе. Те, кто читал стандарт языка С (видите, как я бравирую собственной безграмотностью, и это уже не в первый раз), могут поправить меня в комментариях, но, похоже, что эти макросы внутренней реализации, как и в случае с оператором sizeof, реализацию которого я когда то искал, да так и не нашел, уперевшись в аналогичное выражение. А вот в Keil данные типы реализованы напрямую через стандартные, что заставляет задуматься, а как должно быть на самом деле, или реализация полностью отдана на откуп производителям компилятора?

Еще один вопрос для пытливого читателя — а какие типы мы вообще можем и должны использовать в случае необходимости явного указания размера? С одной стороны, нам рекомендуют применять ни в коем случае не (u)int8_t, реализация которого является опциональной, а fastint8_t, длина которого ограничена снизу, а вовсе не жестко задана, как и у типа atleast8_t. Конечно, опыт показывает что все виды типов с явным указанием размера реализованы во всех практически доступных компиляторах (мне неизвестны обратные случаи, а читатель уже, видимо, заметил мое стремление обобщать личный опыт), но все таки лучше перестраховаться, есть фраза «Лучше выкопать 100 метров окопа, чем 2 метра могилы». С другой стороны, последние два типа не гарантируют нам точную длину, в том ж Kеil они приравнены к короткому целому и никак с байтом не совпадают.

Мое решение — если мне действительно нужен байт, то тип char является правильным, поскольку sizeof(char)==1 жестко зафиксирован стандартом, а вот для целого длиной 16 бит следует применять скорее всего fast16_t, хотя это опять-таки ничего не гарантирует. Поэтому правильным решением будет описывать пакет информации в виде набора байтов, а превращение в внутренние переменные различных типов и обратно из них осуществлять явно прописанными операциями с массивом байтов.

Беззнаковые типы


Сразу же затрону тему беззнаковых чисел. Почему то сложилась практика, что для определении регистров внешних устройств (и при описании структуры пакетов передачи данных) применяют именно беззнаковые типы. Мне не очень понятно такое решение, поскольку единственное различие между знаковым и беззнаковым типом состоит в реализации операций > и <, для знаковых типов анализируется старший бит, а для беззнаковых — бит переноса, но мы ведь не собираемся проводить с ними такие операции. Небольшое различие есть также при преобразовании типов, которое приводит к незначительному изменению быстродействия (по крайней мере, для ARM камней), причем в разные стороны.

В тоже время, ни к каким отрицательным последствиям применение беззнаковых типов не приводит, за исключением того, что нам придется писать лишний символ u, а ведь это идет вразрез с основной парадигмой современного программирования, которая заключается в экономии места на диске, не так ли? Для полей, действительно содержащих информацию, которая интерпретируется как беззнаковое значение, например длину данных в пакете, причем она не может быть отрицательной по определению, применение беззнакового типа позволяет сделать эквивалентными выражения (Len > 0) и (Len != 0), а далее использовать более удобное из них, но для полей, не содержащих подобную информацию, беззнаковый тип не обязателен.

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

Структура файлов


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

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

И раз уж зашел разговор о комментариях, то мой совет — не следует писать комментарии в стиле Counter ++; // Увеличиваем значение счетчика на единицу. Это не шутка, подобные комментарии частенько встречаются в программах, которые авторы не стесняются выкладывать на всеобщее обозрение. Недавно прочитал хорошую формулировку «Комментарии не должны оскорблять читателя». Правда, данное явление больше свойственно Ардуино среде, но, тем не менее, такой комментарий даже хуже его отсутствия, поскольку создают ложное ощущение о своем наличии. А на самом деле тут нет комментария, написана точно такая же операция, просто на немного другом языке программирования, и если существуют среди ожидаемых читателей кода люди, которым комментарии подобного рода помогут понять логику работы программы (а это и есть основное назначение комментария), то единственное, что я могу им посоветовать, это поменять вид деятельности и не связываться с областью, требования которой столь явно превосходят их умственные возможности.

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

Заголовочные файлы


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

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

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

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

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

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

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

Ключевые слова


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

volatile — лишь там, где оно действительно необходимо,
static — везде, где оно не помешает,
const — повсюду, где только можно,
auto и register — будем избегать, поскольку мы с Вами уже договорились, что мы не умнее компилятора, пусть сам справляется.

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

Настройка на компилятор


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

Для начала, нам потребуется настройка на язык, в данном конкретном месте выражающаяся в виде директивы extern C, которая позволит программе на С++ использовать наши функции, оформленные в объектном файле по правилам языка С. Применение данной директивы совершенно необходимо, если мы передаем пакет в виде объектного файла либо библиотеки, но может быть исключено при использовании исходного текста для полной сборки. Я бы рекомендовал при полной сборке данную директиву отключать, но тут решать Вам, можно и оставить. Но сразу замечание по стилю — в исходном примере данная директива прописана напрямую в условном выражении, что лично мне не очень нравится. Я предпочитаю оформлять подобные директивы в виде отдельного включаемого файла, причем в нем же расположено управление реализацией директив и рекомендую именно такой подход.

Итоги


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

P.S.


Первая часть кода, построенная на основе следования вышеприведенным рекомендациям, будет расположена на Githab/tGarryC/Modbus/, когда я наконец то разберусь с Гит.
Поделиться с друзьями
-->

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


  1. f1inx
    20.10.2016 17:58

    sizeof(char)==1 Не значит что он занимает ровно один октет, может два и даже 3 на некоторых реально существующих архитектурах (для которых я писал код).


    The definition of "byte" is defined in clause 3.4 of ISO/IEC 9899:1990
    Basically, a byte is the size of a character. If a character is 16 bits then a byte is 16 bits.


    Также автор этой статьи не до конца понимает зачем нужны стандартные типы:


    • типы (u)intXX_t гарантируют представление знаковых данных в дополнительном коде и размер контейнера
    • типы (u)int_leastXX_t, а не нестандартные at_leastXX_t гарантируют размер контейнера не менее заказанного
    • (u)int_fastXX_t гарантируют оптимальное с точки зрения целочисленной арифметики представление данных и размер мантиссы не менее заказанного

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


    В любом случае использование стандартных типов данных (я имею в виду стандарты ISO C) имеет профит при повторном использовании кода по сравнению с использованием всяких непонятных и мутных типов BYTE, PWORD, at_leastXX, s16 и.т.п.


    Кроме того грамотный разработчик включает код по контролю размеров и endian типов данных используемых в ABI в процедуры elementarу testing.


    1. GarryC
      20.10.2016 18:01

      Вообще то понимаю ). Просто типы (u)intХХ_t не гарантированы в произвольном компиляторе, а гарантированы только остальные.
      Насчет размерв char — спасибо, посмотрю.
      Самому определить — на основе чего и как Вы это собираетесь сделать для компилятора, которого не видите? в этом и был посыл создания стандартных машинонезависимых типов, жаль что он не доведен до логического завершения.
      И сомневаюсь, что сообщение о невозможности компиляции на данном конкретном компиляторе добавит поппулярности Вашей библиотеке — это по поводу контроля типов, хотя лишним точно не будет.


  1. LynXzp
    20.10.2016 23:23
    +2

    Вы извините, но я бы Вам порекомендовал воспользоваться не моим советом: «Чтобы написать что-то хорошее, нужно сначала его написать, а потом удалить 2/3».

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


  1. Sirikid
    21.10.2016 00:22

    Немного пугает это предложение
    > Первая часть кода, построенная на основе следования вышеприведенным рекомендациям, будет расположена на Githab/tGarryC/Modbus/, когда я **наконец то разберусь с Гит**.
    Надеюсь вы не знаете гит потому что раньше пользовались другими VCS.
    Машиннонезависимые типы это C99+, так почему не использовать bool из стандартной библиотеки?


    1. GarryC
      22.10.2016 12:16

      Я просто раньше пользовался оф-лайновыми системами, так что непонятки именно в этом, не пугайтесь :).
      А по поводу стандартного bool — он менее строгий, чем мой, ну и кроме того, у него есть фатальный недостаток.


      1. Sirikid
        22.10.2016 12:47

        Какой? bool, кстати, можно переопределять, для приложения вполне адекватное решение.
        http://ideone.com/JoJIwv


  1. Amomum
    21.10.2016 01:03
    +3

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

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


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

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

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

    typedef enum {False=0,True=1} Boolean;

    Нет ну сколько ж можно-то, ну 2016 год на дворе, стандарту С99 уже 17 лет, ему скоро в университет поступать можно, а вы все еще изобретаете свой bool! Ну есть же stdbool.h, сколько ж можно-то?

    И опять ничего это не дает, неявное приведение к int'у все равно будет! А уж сколько замечательных столкновений с чужими определениями False, True и Boolean можно получить, просто сказка.


    1. Usul
      21.10.2016 20:03

      На счет разницы между знаковыми и беззнаковыми типами дополнительно надо учесть Sign Extension. Например, результат загрузки int8_t и uint8_t в 32-битный регистр будет разным, если старший бит исходного байта ненулевой. Некоторые платформы имеют разные инструкции для загрузки знаковых и беззнаковых байтов (на MIPS — lb и lbu), на некоторых архитектурах для sign extension знаковых данных используется отдельная инструкция. В любом случае, поведение для знакового типа при «расширении» предполагает дополнительную не всегда очевидную операцию. ИМХО, это еще одна причина, почему для представления регистров используют беззнаковые типы.


    1. GarryC
      21.10.2016 20:29

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

      По поводу Boolean — я увидел в коде определение через char и показал более правильный вариант.
      И Вы можете не согласиться, но мой вариант ругается в моем компиляторе (MinGW) при попытке присвоения логическому целого, а стандартный -нет, значит мой безопаснее.
      К сожалению, в операторе сравнении оба варианта допускают целое, я знаю, как добиться правильного поведения в С++, а вот в С — нет.
      Так что в этой части, позвольте остаться при своем мнении и изобретать велосипед.


  1. ProLimit
    21.10.2016 10:24
    +2

    Повсеместно использую беззнаковый uint8_t, uint16_t и совершенно непонтяно, почему нужно искать обходные пути. Притом в статье отсутвует логика: «рекомендовано для встроенного программирования», «поддерживается всеми компиляторами», «но все таки лучше перестраховаться» — и изобретаем свой велосипед. Собственно, такое есть и далее по тексту — иногда непонятно, что же хотел сказать автор. Совет: если хотите донести свои мысли до читателей (а иначе зачем писать?), постарайтесь сформулировать их кратко и подкрепляя примерами кода. Иногда строчка кода понятней абзаца текста.


    1. GarryC
      21.10.2016 20:08

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


  1. Amomum
    21.10.2016 20:57

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

    Вероятно, это дело вкуса, но лично мне не нравится, когда исходники и заголовочные файлы лежат в разных директориях. Почему?
    • Это увеличивает дерево папок проекта, потому что папка каждого модуля (у вас же есть отдельные папки для модулей?) появляются в двух местах.
    • Это заставляет вас либо писать путь в инклуде с какими-нибудь извращениями, вроде #include "../INC/header.h", либо прописывая путь до папки INC в настройках проекта или в makefile.
    • Это не приносит никакой практической пользы.


    Возможно, я каких-то бонусов просто не замечаю?

    Сам я следую принципу «исходник и его заголовок лежат в одной папке, без разделения на SRC и INC», инклуд в исходнике при этом пишется просто #include «header.h». Дерево папок получается компактным, в настройках проекта ничего прописывать не нужно, инклуд получается короче.


    1. GarryC
      22.10.2016 12:10

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


  1. iCpu
    22.10.2016 19:55

    Очередная и не самая удачная статья о том, что перечисления, извините за тавтологию, нужно пихать в типизированное перечисление, а переменные именовать ясно, выбирая их типы исходя из неких предзнаний. Причём, не менее 25% статьи попадает в раздел вкусовщины (слава Страуструпу, не было ни слова ни о фигурных скобках, ни о порядке сравнения в условиях (это про 1 == Один, если кто забыл)).

    Рискую быть непопулярным, но разве для вашего графоманства не нашлось темы поострее? Чего-нибудь менее замызганное и более желтушное? Например, «Предварительные оптимизации: 34 способа сделать вашей программе быстро.», «Стандарты C++: мифы и реальность» или «Течка памяти: случка указателей». Ну или «186 относительно честных способов отъёма прав администратора», на худой конец.


    1. GarryC
      23.10.2016 23:17

      Раз уж Вы задали такой резкий тон, то 1==ОДИН забанено раз и навсегда и действительно надежный стиль MISRA решил этот вопрос запретив использовать результат присвоения в оператора сравнения. Раз уж у Вас столь обширные познания, то такую элементарную вещь надо было бы знать.

      Почему то Джек Гансли не стесняется рассказывать на своих семинарах очевидные вещи, Барр в своем недельном семинаре 4 раза напоминает о перечислениях и их преимуществах а в моем опусе это приравнено к графомании. Я в начале ясно написал, в чем цель настоящего поста и ее достиг, на мой взгляд. Или Вы опять таки из тех, кто заявил в опросе, что мог бы прочитать такой курс лекций? Пока не вижу ни одного Вашего поста, который подтвердил бы Ваши притязания.

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

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


      1. iCpu
        24.10.2016 04:21

        Не решает MIRSA никаких особых проблем со случайными присвоениями вместо сравнения, да и не может решить. Кроме того, не видел, чтоб там что-то такое банили, хотя, конечно, книжку 12 года не читал. Мне, например, не понятно, как защититься от опечатки if (Data = 0) вместо if (Data == 0) или if (Data != 0), и никто мне не даст ответ. Нет его.

        Почему выступления перед студенческой публикой позволяют раз за разом поднимать подобную проблему? Потому как семинар и публикация на хабре, на котором каждый второй хвалит перечисления, — немного разные вещи. Потому как на семинаре не выходят 3-4 выступающих подряд хвалить одну и туже вещь, для которой хватило бы двух строчек. Собственно, это и есть основная моя претензия: всё вами изложенное могло поместиться в 33 строчки и не потеряло бы ни грамма смысловой нагрузки.

        Я нахожусь в удобном положении читателя. Мне, как читателю, не обязательно указывать решения — достаточно поставить проблему. *Избитый пример про повара и блюдо*
        Я поставил проблему: вы затянуто перечисляете либо очевидные молодому программисту вещи (тут я беру выпускников ВУЗов '12 года и позднее), либо рассуждаете о вкусовщине. При этом не раскрываете толком ничего. Вам, например, нужно иметь текстовик иметь с описанием имеющихся модулей, а мне вынь да положь doxygen'а кусок. Оба подхода имеют право на существование, но мой, как более современный, просто красивее. И ни один из них не спасает от ленивца, не заполняющего документацию, или заполняющего её не так, как вам нравится.
        Одна проблема в ваших, да и не только ваших, публикациях очень ясно прослеживается — вы не даёте публикации настояться, дойти, исправить ошибки и убрать лишние куски текста. Я понимаю, иногда это сложно сделать, и нужно дать кому-либо оценить твой текст не предвзято. Я-то в чём виноват, когда издеваюсь над вашими прорехами? Не делайте их — и всё!

        Ну и в заключении, о переходе на личности. Вы зашли на очень скользкую дорожку — оценку профессионализма по личностным характеристикам или оценке окружения. Если вы познакомитесь с достаточным числом программистов, ваша статистика вас разочарует.
        Мой рейтинг не означает ничего, для его потери достаточно просто зайти в ветку с политикой или написать фанатам javascript, что он медленный или даже «а производительность этой фичи замеряли?». Мои публикации и их отсутствие так же не означают ничего, т.к. то, что я не нахожу в своей повседневной деятельности того, о чём имеет смысл написать не означает, что я ничего не понимаю. Рутина, знаете ли, не самая интересная вещь для публикации, но достаточно полезная для мозга в смысле поиска способов избавления себя от неё.
        А вот о вас ваш призыв мериться рейтингами говорит достаточно много. Грубый наезд «да кто ты такой?!». Держу пари, если бы я стоял рядом, получил бы уже в глаз. Но проверять это, я, конечно же, не стану.


        1. GarryC
          24.10.2016 10:16

          Защититься от опечатки, которую Вы привели, очень просто и ответ дан давно, по крайней мере лет десять назад. Достаточно поставить поддержку MISRA правил и она Вам мгновенно укажет на неправильное место в коде. И это правильный подход, потому что пропагандируемое if (0==Flag) создает ложное ощущение безопасности, поскольку не защищает от опечатки if (Flag=Flag1), поэтому было принято именно такое радикальное решение.

          Я не очень понял про студенческую публику — перечисленные мной авторы курсов ориентированы скорее на профессионалов и курсы повышения квалификации.

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

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

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

          А про переход на личности — не я первый начал. Подразумевалось, что человек, обличающий в другом графоманские наклонности, должен быть готовым предъявить свои достижения, иначе его высказывания можно охарактеризовать известной цитатой «Критика Дон Жуана импотентами, при всей ее объективной справедливости, оставляет неприятный осадок».
          Крайне огорчительно, что Вы в своей повседневной деятельности не находите ничего, о чем был бы смысл написать. Вы настолько не любите свою работу? Я в своей нахожу много интересного, вот и делюсь своими находками. Мои опусы читают, их заносят в избранное, значит, они не бесполезны.
          Насчет получить в глаз — это Вы погорячились, это не в моем стиле, я думал, что это очевидно, но, видимо, ошибался.

          PS. Рад, что образчики юмора упомянутого типа из вашего комментария исчезли.


          1. iCpu
            24.10.2016 11:31

            Ах, поставить поддержку!.. Я бы понял, если бы предложили макрос #define EQUAL == А так получается, что без синтаксического анализатора никак? Ну и зачем вы тогда вообще об этом вспомнили?

            Опять же, иной формат изложения.

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

            Опять же, «чукча не писатель». Я не обязан показывать вам пример. Мне достаточно поставить проблему. Но если вы настаиваете, пункты «имена переменных» и «выражайтесь ясно» можно объединить в «пишите понятный код»:
            «Какой код проще всего понять? Хорошо документированный или хорошо написанный? Сравните
            int foo(int a, int b) // a power b
            {
            int c = 1;
            while(b--) c *= a;
            return c;
            }
            или
            int power (int base, uint8_t exponent)
            {
            int product = 1;
            for (uint8_t currentExponent = 1; currentExponent <= exponent; ++currentExponent ) {
            product *= base;
            }
            return product;
            }
            Думаю, пояснения излишни. Пишите красивый и понятной код, ведь делать это просто, а читать — приятно. Конечно, далеко не всегда можно написать полностью самодокументируемый код, но оно и не нужно. Сегодня IDE и компиляторы всё равно делают за вас большую часть работы, так что не пишите для них. Пишите для людей: используйте понятные имена всех переменных и функций, помогайте компилятору и пользователям с помощью ключевых слов. Если знаете точный тип входных параметров, указывайте его явно. Используйте правильные формы имён: переменная — существительное, функция — глагол, флаги — вопросительное предложение и тп. И не заменяйте без особой надобности понятные записи алгоритмов на хитро оптимизированные. Не экономьте на спичках.»
            Заметьте, по смыслу я практически полностью пересказал ваш текст. По объёму же получилось раза в 4 меньше, что гораздо проще читается. Неплохо?

            Да-да, конечно, «обсуждение дам с девственниками». Слыхали уже. А теперь ответьте, вы сами читали свою статью? Как читатель, а не автор? Нет? Ну, тогда что мне с вами обсуждать? Получается, что ВЫ, мой уважаемый автор, тот самый «импотент», чья критика моих комментариев «при всей ее объективной справедливости, оставляет неприятный осадок». Вы предвзяты в своих суждениях, так как для вас любая критика — не просто чьё-либо мнение, а угроза вашему детищу. Я не могу осуждать вас за это. Хотя нет, постойте, могу.

            PS. Рано радуетесь, он никуда не делся. Просто, кроме всего прочего, я наивно добрый человек, который действительно хочет помочь людям. А это требует несколько иного слога.

            PPS Хватит мериться письками, у кого сколько рейтинга или публикаций. О чём предлагаете мне написать: «Парсинг хранимых процедур postgres для генерации исходников C++»? «Деобфускация инкриментных билдов java-приложений на примере клиента MMORPG»? Если для второго дострою тулзу, позволяющую транслировать имена между версиями, быть может, напишу. А так…

            PPPS. Естественно, при отрицательном рейтинге тег CODE у меня не работает, так что кушайте так.


            1. GarryC
              24.10.2016 13:58

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

              «Заметьте, по смыслу я практически полностью пересказал ваш текст. По объёму же получилось раза в 4 меньше, что гораздо проще читается. Неплохо» — напоминает книжки «Вся русская классика 18-20 веков в 20 страницах», Вы не находите? Я ориентируюсь на другие эталоны — открываю Майерса и «правило 2: Предпочитайте const enum и inline использованию #define» занимает места больше, чем весь мой скромный опус. Мне кажется, что люди класса Майерса и Гансли знают о том, как надо рассказывать об особенностях встроенного программирования побольше, чем я (насчет Вас не уверен, судя по Вашему апломбу), поэтому с моей стороны вполне логично на них ориентироваться.

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


              1. iCpu
                24.10.2016 15:39

                Про MISRA — вы. И именно это у меня вызывает недоумение, ведь это ряд не самых удобных и, порой, спорных рекомендаций. Который ещё и не в тему пришёлся. Но да ладно, оставим.

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

                Прекратите выворачивать мои слова. Я написал, что мне не интересно писать о чём-либо ординарном, а не о том, что мне не интересна моя работа. Как только я открою для себя что-то действительно новое, вы обязательно увидите об этом пост. Но распыляться по мелочам ради циферок… Нет уж, спасибо.


                1. GarryC
                  24.10.2016 16:27

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

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

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


                  1. iCpu
                    24.10.2016 17:06

                    Есть тот же HICPP, который во многом расходится с MISRA, и который так же написан кровью, потом и опечатками. ВКУСОВЩИНА. Нет идеального способа писать без ошибок, нет даже универсального способа уменьшить их число до минимума.

                    Вот смотрите, я перегружаю в своём классе operator bool() const и запись if (myClass) становится осмысленной. Дальше я переопределяю operator bool() const класса shared_ptr, избавляясь от него, и лишаюсь ошибки случайной проверки указателя вместо класса. Далее, оборачиваю разные кейсы условий в отдельные методы, и избавляюсь от символов сравнения как таковых, а модификаторы const практически полностью защищают меня от случайного приравнивания. В итоге, я получаю куда более простую запись, создающую куда меньше ошибок. Далее, втыкаю в каждую дырку по constexpr, и получаю во многом эквивалентный вашему код, а где-то даже более оптимальный. С этой точки зрения ваш вариант с перечислениями недостаточно хорош. Единственная проблема — трудозатраты, так как идеальный код мы пишем для себя, а на работе нужно решать текущие проблемы, даже если тебе не нравится решение.

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


                    1. GarryC
                      24.10.2016 17:19

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

                      Может быть, Вы не заметили (хотя я честно написал, что знаю как сделать все правильно в С++, а в С не знаю), но в моем посте речь шла о чистом С. В плюсах существует множество полезных фич, это абсолютно верно, но они все не сработают в обычном С, даже в С99. Так что Ваш пафос абсолютно не по адресу. Можно было бы еще вспомнить жесткую типизацию в Аде, пользы принесло ровно столько же.


                      1. iCpu
                        24.10.2016 17:31

                        Из-за того, что Тайота наговнокодила, после чего их тачки могли втапливать педаль газа вместо тормоза? Само по себе отклонение от MISRA не было причиной исков, это было доказательство халатности компании при разработке и тестировании.

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


                        1. GarryC
                          24.10.2016 17:38

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

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


                          1. iCpu
                            24.10.2016 18:16

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

                            Что ж, похоже, мы достаточно друг с другом поспорили, пора и честь знать. Предлагаю вам в качестве пробы последовать моему совету и включать поменьше теории и умозрительных экспериментов в ваши статьи (или выносить её в спойлеры, оставляя в основной статье короткую выжимку). Попробуйте уложиться в 3-4 минуты чтения, отводя на введение не более 100 слов. Приводите максимально подробные примеры кода, но не более 15 строк за раз. Посмотрите на реакцию читателей, как они отреагируют на такой формат. А там уж посмотрим, может ли свежее поколение блеснуть знаниями перед бывалыми кодерами, или это очередное позерство выскочки-шестиклассника с его смехуючками.