Oberon space
General Category => Общий раздел => Тема начата: Romiras от Ноябрь 15, 2013, 05:48:44 pm
-
Наткнулся на любопытную статью: Качество встраиваемого ПО или погром всё-таки случился (http://ko.com.ua/kachestvo_vstraivaemogo_po_ili_pogrom_vsyo-taki_sluchilsya_98518).
«это позорный образец проектирования и разработки ПО»
в firmware, решающем эту задачу, надстроенным над операционной системой реального времени, экспертиза выявила… одиннадцать тысяч глобальных переменных.
Соблюдение отраслевого стандарта кодирования (для автомобильной промышленности такой есть, даже целое семейство, совокупно называемое MISRA) характеризуется выявленным числом его нарушений – их набралось 80 тысяч (в Toyota принят свой внутренний стандарт, который заимствует из MISRA всего 11 правил, при минимально требованных во время написания кода 93-х). По ходу дела было выявлено, что в такой сложной системе полностью отсутствует учёт сбоев и ошибок.
Мне думается, это связано с тем, что над разработкой встроенного ПО работают по большей части инженеры-электронщики, у которых нет достаточной квалификации и также культуры программирования.
-
Мне думается, это связано с тем, что над разработкой встроенного ПО работают по большей части инженеры-электронщики, у которых нет достаточной квалификации и также культуры программирования.
Угу. Много глобальных переменных. Ну и что? Работает ведь... Тут даже не знаешь с какой стороны подходить. Просто запретить глобальные переменные числом больше N - не поможет никак...
-
Угу. Много глобальных переменных. Ну и что? Работает ведь... Тут даже не знаешь с какой стороны подходить. Просто запретить глобальные переменные числом больше N - не поможет никак...
А вот если обязать предоставлять исходные коды владельцу автомобиля...
-
Мне думается, это связано с тем, что над разработкой встроенного ПО работают по большей части инженеры-электронщики, у которых нет достаточной квалификации и также культуры программирования.
Угу. Много глобальных переменных. Ну и что? Работает ведь... Тут даже не знаешь с какой стороны подходить. Просто запретить глобальные переменные числом больше N - не поможет никак...
В стандарте должны быть описаны метрики, по которым производится сравнение. Скажем, количество глобальных переменных в процентном соотношении от общего количества всех переменных, с учётом специфики производства встроенного ПО.
-
В стандарте должны быть описаны метрики, по которым производится сравнение. Скажем, количество глобальных переменных в процентном соотношении от общего количества всех переменных, с учётом специфики производства встроенного ПО.
В смысле все метрики легко обходятся индусами абсолютно без повышения качества. Те же глобальные переменные превратятся в одну:
struct global_t
{
int n_23;
char c_a;
.... 100500 полей ...
} global;
P.S. Причем конкретно вот такой пример я видел неоднократно в сишных проектах. Авторы, видимо, где-то слышали о вреде глобальных переменных... но сути не поняли.
-
Если код раскрыть ну никак нельзя, то может просто независимые эксперты должны на него смотреть... Но тут тоже широкое поле для предвзятостей и нарушений.
-
Можно хотя бы вот так:
Цикломатическая сложность (http://ru.wikipedia.org/wiki/%D0%A6%D0%B8%D0%BA%D0%BB%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C)
Одно из оригинальных предложений Мак-Кейба в том, что необходимо ограничивать сложность программ в течение их разработки; он рекомендует, чтобы программистов обязывали считать сложность разрабатываемых ими модулей, и разделять модули на более мелкие всякий раз, когда цикломатическая сложность этих модулей превысит десять.
-
разделять модули на более мелкие всякий раз, когда цикломатическая сложность этих модулей превысит десять.
Ну будет мильен модулей с мильеном зависимостей. Это очень хорошо, что есть математические методы. Но они если и имеют какое-то практическое применение, то уже непосредственно к полученному результату и как критерий "необходимости", но не "достаточности". Обязывать непосредственно программистов считать количество IF/WHILE'ов и по результатам бить на модули - глупость в современных реалиях. Метод предложен в 1976 году, тогда и представить не могли всех высот абстракций, на которые может забраться современный программист. Соответственно, сделать гавно можно и без циклов. И отобрать средства для создания мощных абстракций тоже нельзя - ибо в умелых руках оно позволяет достигать надежности, недостижимой более низкоуровневыми средствами.
-
Не знаю, что такое ц.с. модулей, но что такое ц.с. процедур я прочувствовал на практике очень хорошо.
У меня есть одна разработка (рожденная в адских условиях без ТЗ). Это самый страшный говнокод, который мне довелось видеть в продакшене. Написал эту гадость я.
Хотя код выглядит на первый взгляд довольно прилично. Никаких глобальных переменных. Все аккуратно написано и хорошо прокомментировано (на этом у меня перфекционизьм)
НО!
4000 строк кода (самая большая процедура ~1000 строк)
ц.с. около 100
Понять это просто невозможно. Сам я правлю эту гадость только после двух часов погружения.
И я уверен на 101%, что для облагораживания кода достаточно разбить большие сложные процедуры на более мелкие и обозримые. По отдельности их можно легко вкурить.
Разделяй и властвуй!
-
Соответственно, сделать гавно можно и без циклов.
Можно. Но разве это утверждение отменяет проблему цикломатической сложности?
-
В стандарте должны быть описаны метрики, по которым производится сравнение. Скажем, количество глобальных переменных в процентном соотношении от общего количества всех переменных, с учётом специфики производства встроенного ПО.
В смысле все метрики легко обходятся индусами абсолютно без повышения качества. Те же глобальные переменные превратятся в одну:
struct global_t
{
int n_23;
char c_a;
.... 100500 полей ...
} global;
P.S. Причем конкретно вот такой пример я видел неоднократно в сишных проектах. Авторы, видимо, где-то слышали о вреде глобальных переменных... но сути не поняли.
В мире сугубо встроенных систем это было когда-то не злом, а - суровой необходимостью.
Так неким образом боролись с желанием компилятора распихать статически объявленные переменные по ему угодным адресам.
Относительно Недолго.
Где-то между поздним кайнозоем и раннем антропогеном. :)
От индусского кода именно и отличалось осмысленным вступанием на территорию Абсолютного Зла.
Хотя, как это часто бывает в играх с Нехорошим, контроль над сложностью и осмысленной поддержкой срывался в самом неожиданном месте.
У меня сейчас на поддержке проект на Дельфи, в котором автор не продумал дерево наследования. Вложенные во владельцев объекты обращаются к полям владельцев через указатели на поля (которые инициализируются при создании вложенных объектов). Сейчас приступил к перепроектированию дерева и перетасованию функциональностей по уровням наследования, выделению общих частей, перепродумыванию интерфейсных частей. Ухожу от зацикливания в импорте модулей потихоньку.
Кроме того, есть два огромных куска подсистемы (просто оси графиков и общая горизонтальная ось времени для всех графиков), абсолютно дублирующих функциональность друг друга (различаясь именами полей, но, с практически одинаковыми методами, по смыслу, например в осях параметров тики, метки значений и сетки имеют Y координаты, а на оси времени - по X). Ну, и т.д.... Конец года и другие проекты подпирают, а тут ещё с этим разгребаться....... :)
-
И я уверен на 101%, что для облагораживания кода достаточно разбить большие сложные процедуры на более мелкие и обозримые. По отдельности их можно легко вкурить.
Если бывсё было так однозначно просто!
Только - без фанатизьму!
Можно утонуть в процедурах и методах.
Важно не количество, а уместность принятого архитектурного решения.
-
Соответственно, сделать гавно можно и без циклов.
Можно. Но разве это утверждение отменяет проблему цикломатической сложности?
Нет, не отменяет, но обесценивает :) Вот еще иллюстрация - свежак, в пятницу писано:
typedef boost::multi_index_container<
object_ref
, boost::multi_index::indexed_by<
boost::multi_index::ordered_unique< boost::multi_index::global_fun<object_ref const&,object_id,&get_object_id_id> >
, boost::multi_index::ordered_non_unique< boost::multi_index::global_fun<object_ref const&,object_type,&get_object_type> > > >
container_t;
typedef container_t::nth_index<0>::type by_id_t;
typedef container_t::nth_index<1>::type by_object_type_t;
container_t container;
Какая тут цикломатическая сложность и какое она имеет значение?
P.S. Если интересно, то это ассоциативный контейнер объектов с двумя индексами - по идентификатору (уникальный) и по типу (не уникальный).
-
Не понял. А что тут сложного или непонятного?
-
Не понял. А что тут сложного или непонятного?
Для собеседования при приёме на фирму - самое то! 8)
-
Пугать людей синтаксисом плюсовых шаблонных коллекций? :D
-
Пугать людей синтаксисом плюсовых шаблонных коллекций? :D
Ну перепиши на другом синтаксисе и покажи результат :-) Семантика должна остаться та же естественно.
-
Не совсем понял, что ты подразумеваешь под семантикой в данном случае, но вот примерный аналог на 1С:
// описание типов
ТипЧисло15_2 = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ТипУказательНаЗапись = Новый ОписаниеТипов("Структура");
// описание коллекции
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Ключ1", ТипЧисло15_2); // типизированный ключ
Таблица.Колонки.Добавить("Ключ2"); // произвольный тип ключа
Таблица.Колонки.Добавить("Ключ3"); // произвольный тип ключа
// и т.д.
Таблица.Колонки.Добавить("Значение1", ТипУказательНаЗапись); // типизированное значение
Таблица.Колонки.Добавить("Значение2"); // произвольный тип значения
// и т.д.
Таблица.Индексы.Добавить("Ключ1"); // простой индекс
Таблица.Индексы.Добавить("Ключ2, Ключ3"); // составной индекс
// Использование:
// поиск по составному индексу
Отбор = Новый Структура("Ключ1, Ключ2", Ключ1, Ключ2);
ИскомыеСтроки = Таблица.НайтиСтроки(Отбор);
Эта коллекция не 1:1 boost::multi_index_container, но смысл примерно тот же (с поправкой на динамическую типизацию)
Синтаксис простой и деревянный. :)
ps Надеюсь, я правильно понимаю, что такое boost::multi_index_container...
-
Не совсем понял, что ты подразумеваешь под семантикой в данном случае, но вот примерный аналог на 1С:
// описание типов
ТипЧисло15_2 = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ТипУказательНаЗапись = Новый ОписаниеТипов("Структура");
// описание коллекции
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Ключ1", ТипЧисло15_2); // типизированный ключ
Таблица.Колонки.Добавить("Ключ2"); // произвольный тип ключа
Таблица.Колонки.Добавить("Ключ3"); // произвольный тип ключа
// и т.д.
Таблица.Колонки.Добавить("Значение1", ТипУказательНаЗапись); // типизированное значение
Таблица.Колонки.Добавить("Значение2"); // произвольный тип значения
// и т.д.
Таблица.Индексы.Добавить("Ключ1"); // простой индекс
Таблица.Индексы.Добавить("Ключ2, Ключ3"); // составной индекс
// Использование:
// поиск по составному индексу
Отбор = Новый Структура("Ключ1, Ключ2", Ключ1, Ключ2);
ИскомыеСтроки = Таблица.НайтиСтроки(Отбор);
Эта коллекция не 1:1 boost::multi_index_container, но смысл примерно тот же (с поправкой на динамическую типизацию)
Синтаксис простой и деревянный. :)
ps Надеюсь, я правильно понимаю, что такое boost::multi_index_container...
На 1С повторить семантику приведенного владом кода невозможно. Просто потому, что 1С динамически типизирован.
-
Не понял. А что тут сложного или непонятного?
Я этот код привел не потому, что хотел попугать плюсовыми шаблонами. Это все на тему того, что цикломатическая сложность мало чего показывает и ее непонятно как считать для языков уровнем выше ассемблера. Еще более простой пример: плюсовый std::for_each - это цикл, который должен быть подсчитан во всех местах использования или не цикл?
-
Не знаю как там считать форычи, но проще всего понять ц.с. на IF'ах
Чем больше условных операторов, тем сложнее понять код, т.к. каждый IF - это отдельный сценарий выполнения.
http://msdn.microsoft.com/ru-ru/library/ms182212.aspx
-
Вот ц.с. 10
// path1
IF cond1 THEN
// path2
IF cond2 THEN
// path3
IF cond3 THEN
// path4
END;
END;
END;
IF cond4 THEN
// path5
IF cond5 THEN
// path6
IF cond6 THEN
// path7
END;
END;
END;
IF cond7 THEN
// path8
IF cond8 THEN
// path9
IF cond9 THEN
// path10
END;
END;
END;
Теперь представь себе процедуру сложности 100 и выше.
ps Кажется бредом, но это реально отражает сложность кода. Т.к. для понимания алгоритма нужно "загрузить" в мозг все возможные сценарии и проанализировать. А "загрузить" более 10 сценариев уже сложно, ибо не могет больше мозг.
-
Не знаю как там считать форычи, но проще всего понять ц.с. на IF'ах
Чем больше условных операторов, тем сложнее понять код, т.к. каждый IF - это отдельный сценарий выполнения.
http://msdn.microsoft.com/ru-ru/library/ms182212.aspx
Дык ветвление это совсем не обязательно if/switch/for :-) Это может быть банально вычисление индекса с последующим исполнением функции указатель на которую хранится по соответствующей ячейке массива :-) И ни одного if'a не будет вообще, только чистая арифметика :-)
-
Ну я верю, что на сях можно написать абсолютно прямолинейный и абсолютно адский код. :D
Но как я уже говорил, ц.с. это не отменяет не в коем разе.
Т.е. если у тебя ц.с. в коде 50 то не имеет никакого значения, что там вычисляется на указателях, т.к. это уже говно по дефолту.
И еще:
ц.с. - это не абсолютная метрика. Это лишь одна из...
Выделяется она лишь тем, что ее легко подсчитать, она очевидна и достоверна.
Т.е. это первое что нужно проверять. Если ц.с. удовлетворительная, то можно и другие метрики померить для надежности.
-
Можно на реальный код посмотреть:
ц.с. 9 если не ошибаюсь.
PROCEDURE (tree: Tree) Delete* (IN data: Data), NEW;
VAR
node, parent, temp: Node;
BEGIN
node := Search(tree.root, data);
IF node # NIL THEN
IF (node.left # NIL) OR (node.right # NIL) THEN
parent := Next(node);
ELSE
parent := node;
END;
IF parent.left # NIL THEN
temp := parent.left;
ELSE
temp := parent.right;
END;
IF temp # NIL THEN
temp.parent := parent.parent;
END;
IF temp.parent = NIL THEN
tree.root := temp;
ELSE
IF (parent.parent.left = parent) THEN
parent.parent.left := temp;
ELSE
parent.parent.right := temp;
END;
END;
IF parent # node THEN
node.CopyFrom(parent);
node.color := parent.color;
END;
IF parent.color = black THEN
DeleteFixUp(tree, temp);
END;
parent.color := deleted;
END;
END Delete;
Легко понять? Где ошибка? (она есть)
-
Не знаю как там считать форычи, но проще всего понять ц.с. на IF'ах
У меня нет проблем с пониманием :) У меня проблема с тем, что профит маленький (хотя и ненулевой). Т.е., сабжевая проблема не может быть принципиально улучшена только за счет подсчета ифов.
-
Я пытаюсь донести мысль что если сложность > 10, то все остальное уже не имеет смысла анализировать, т.к. для начала надо ц.с. привести в порядок.
Скажу иначе. Если у тебя в коде большая ц.с. то, что бы ты там не улучшал помимо ц.с., лучше код не станет.
-
Ну я верю, что на сях можно написать абсолютно прямолинейный и абсолютно адский код. :D
Это и на Обероне можно, да и на жабе, да где угодно. :-)
-
Легко понять?
Прошу обратить внимание на этот IF
IF (node.left # NIL) OR (node.right # NIL) THEN
parent := Next(node);
ELSE
parent := node;
END;
Это очень хороший пример того, как сценарий расщепляется на два. Сразу в голове нужно представлять две разные картины. В одной картине parent - это удаляемый узел, в другой это его сосед. И дальше при чтении нужно удерживать эти две картины, а оно там еще расщепляется... Либо можно читать весь код для одного сценария, потом для второго, потом для третьего и т.д.
И это еще нормальный код.
-
Пугать людей синтаксисом плюсовых шаблонных коллекций? :D
Ну перепиши на другом синтаксисе и покажи результат :-) Семантика должна остаться та же естественно.
Честно и откровенно?!
Даже не хочу ни одну из извилин напрягать, что там вообще такое записано!
Просто потому, что это не из моих проектов или заданий. :)
Кактотак...