Третий момент -- о природе строительных кирпичей. Для целей сохранения хорошего порядка руководство по стилю, содержащее многослойный винегрет из соглашений об именовании, идиом и примеров модулей для разработчиков будет служить служить вам лучше, чем коллекция императивных (обязательных к исполнению) инструкций, ограничивающих способность программиста искать элегантность самостоятельно. Если вы не можете доверить команде самостоятельно решить, когда заключать блок в скобки, а когда нет, как вы можете доверить ей написать ваш суперпроект?
Четвертый момент -- об этих императивах. Есть некоторые средства, которые вообще не стоило изобретать, например scanf() и gets()в UNIX. Указания никогда не использовать их в разрабатываемом коде разумны. Но есть цели, которых просто невозможно безопасно достичь другим, лучшим путем. И всегда остается проблема баланса ясности. Мы рассмотрим два конкретных примера, из которых станет видно, что в Си все же существует хороший повод для использования goto -- хотя вам могли говорить, что таких поводов нет.
Вот первый, здесь нет другого способа. Представьте рекурсивный обход двоичного дерева:
void Walk(NODE *Node){// Do whatever we came here to do...// Shall we recurse left?if(Node->Left) Walk(Node->Left);// Shall we recurse right?if(Node->Right) Walk(Node->Right);}
По мере обхода дерева, мы начинаем делаем вызовы, ведущие нас влево, влево, влево, влево ... и так до самого низа. Затем просматриваем каждую комбинацию влево, влево, вправо, вправо, влево... и т.д., до тех пор, пока не просмотрим все ветви, и не проделаем все возвраты из нашего последнего визита, который был вправо, вправо, вправо, вправо....
Каждый шаг влево или вправо включает в себя открытие нового кадра стека, копирование аргумента в этот кадр стека, выполнение вызова и возврат. Для некоторых задач с многочисленной навигацией, но небольшой обработкой в узле, эти накладные расходы могут оказаться чрезмерными. Но посмотрите на такую мощную идиому, известную как устранение хвостовой рекурсии:
void Walk(NODE *Node){Label:// Do whatever we came here to do...// Shall we recurse left?if(Node->Left) Walk(Node->Left);// Shall we recurse right?if(Node->Right){// Tail recursion elimination used for efficiencyNode = Node->Right;goto Label;}}
Мы используем стек для отслеживания того, куда мы попали слева, но после того, как мы обошли левую часть, переход на правую ветвь не требует хранения положения. Поэтому мы устраняем 50% вызовов и накладные расходы на возвраты. Это может изменить ситуацию при выборе между реализацией на Си или добавлением ассемблерного модуля. Чтобы увидеть другой пример, посмотрите на конструкцию Даффа (Duff's Device) в книге Страуструпа "Язык программирования C++" (Stroustrup's The C++ Programming Language ).
Второй пример касается чистоты стиля -- вообще нет необходимости применять язык ассемблера. Помните, что когда Дейкстра посчитал goto вредным, он имел в виду привычку использовать goto для организации управления в неструктурированном коде 60-х годов. Идея заключалась в том, что меньше используя goto мы могли бы улучшить ясность. Идея не состояла в жертвовании ясностью избегая goto любой ценой. Представьте программу, которой нужно открыть порт, инициализировать его, инициализировать модем, установить соединение, зарегистрироваться (logon) и загрузить файл (download). Если что-то не так, в любом месте, нам нужно вернуться обратно в самое начало. Доморощенный структуралист мог бы написать нечто вроде:
BOOL Done = FALSE; while(!Done) { if(OpenPort()) { if(InitPort()) { if(InitModem()) { if(SetupConnection()) { if(Logon()) { if(Fetch()) { Done = TRUE; // Ouch! Hit the right hand side! } } } } } } }
Что нам кажется просто глупым. Есть более понятная альтернатива, использующая то, что оператор && прекращается сразу, как только встречается выражение, принимающее значение FALSE -- "неправильное использование" языка, обычно запрещаемое в большинстве стандартов кодирования:
while(!(OpenPort()&& InitPort()&& InitModem()&& SetupConnection()&& Logon()&& Fetch()));
Здесь все ясно и удобно, поскольку мы можем инкапсулировать каждый шаг в функцию. Проблема в коде такого рода заключается в том, что требуется правильно сделать целый ряд ужасных вещей, например инициализацию строк и т.п., и чтобы работать с таким кодом, нужно выполнить его в очень похожем на скрипт виде. Например, так:
Start: if(!OpenPort())goto Start; if(!InitPort())goto Start; if(!InitModem())goto Start; if(!SetupConnection())goto Start; if(!Logon())goto Start; if(!Fetch())goto Start;
Это в точности то, что позволяют нам делать специализированные скриптовые языки, разработанные для такого вида работ!
Не забывайте, если вы хотите, чтобы предмет вашего обожания понял ваше любовное письмо, вы не позволите педантизму правописания и грамматики исказить письмо, а если вы хотите, чтобы ваши коллеги поняли вашу программу, не перекручивайте ее структуру во имя "чистоты".
Значимые метрики
Мы можем повернуть линзы практического понимания цели на получение и интерпретацию метрик, на которые в некоторым местах тратится огромное количество денег, что заставляет нас думать, что они правильные.
Имеется три мотива коллекционирования чисел. Все они ценные, но всегда важно понимать, в чем состоит наше намерение. Их три:
Описательная наука. Сюда относятся получение и коллекционирование данных о предмете, чтобы увидеть, можно ли найти какие-либо интересные свойства данных. При этом не нужно знать, что ожидают найти. Нецензурированные сырые данные -- источник всего. Современная этимология в огромном долгу перед леди времен Виктории и Эдварда, которые проводили свое время в создании очень детальных акварелей каждой бабочки или насекомого, которых они только могли найти. Уже стало традицией, что действительно интересные кометы одновременно открываются профессионалами и астрономами-любителями. Наша дисциплина пострадала от непродуманного переноса "метрик" массового производства на интеллектуальную, трудоемкую деятельность. Если мы хотим построить аналогии с фабриками, то нам нужно задать вопрос о том, что оптимизирует сложные человеческие факторы в нашем производстве. Нам нужно проводить больше времени у истоков. Например, много ли дает знание того, что проверка потерпела неудачу в коде, написанном летом, когда есть много других вещей, которые мы могли бы сделать вместо того, чтобы поставить время проверки? Можно заложить окна кирпичом или сезонный фактор в планы работы предприятия, чтобы максимизировать качество. Что есть индикатор качества? Внутренние или внешние отчеты об ошибках на одну функцию? Число строк кода на одну функцию?