Цитата:
...
Кстати, если говорить об источниках алгоритмических ошибок, то для правильно обученного (владеющего методом Дейкстры и упрощёнными, базовыми паттернами циклов - viewforum.php?f=82) программиста те же циклы вообще перестают быть источником ошибок; а вот развесистое принятие решений с кучей условий никакими методами, кроме смысловой проверки, не поборешь. И нужно представление алогоритма в какой-либо форме, облегчающей "аудит кода"..
И в этом контексте не лишним будет в очередной раз затронуть тему о, всё-таки, ограниченной эффективности дракон-представления алгоритмов. Да, дракон-схемы оправдывают свой хлеб на каком-то уровне, но только на этом форуме немало разговоров насчёт веточных циклов, о потенциальных неструктурных goto-переходах, да и в целом, о реальном когнитивном восприятии графического направленного потока управления. Например, вспоминается эта тема:
Преобразование графов. Основная идея - "двумерное структурное программирование" (если не ошибаюсь с термином) - вроде как, должна ликвидировать недостатки выразительности средств классического текстового представления алгоритмов, но эта "классика" тоже продвигается вперёд.
Рекомендую глянуть на проект
LiveScript - это развитие популярного CoffeeScript - фактически, это препроцессор для генерации кода на JavaScript. Его цель - удобное наглядное представление кода, императивное с "функциональными" мотивами. Во-первых, это пример "когнитивного" кода, без лишнего информационного шума (пусть пока потенциально, в этом языке имеются проблемы, и речь идёт не только о синтаксическом сахаре, которого кушать нужно тоже в меру. И на сайте удобно показано сравнение с "классическим" кодом). И, во-вторых, это база для ряда алгоритмических техник, которые оправдали себя на практике (не только в этом языке).
Несколько простых примеров. Сейчас в императивном коде всё больше и больше "драконовский поезд" по развилкам "схлопывается" в базовые элементарные линейные структуры, как-то так:
Код:
# "if" как вычислительное выражение (а не оператор) + постфиксная форма:
var1 = value1
var2 = value2_1 if param1 > 0 else value2_2
var3 = value3
# "Elvis"-оператор:
x = param?.field?.value
# что аналогично:
if (param <> null) and (param.field <> null) then
x = param.field.value
else
x = null
Есть потенциал делать менее развесистыми и управляющие алгоритмы. Картинка ниже взята из статьи Ермаков И. Е., Жигуненко Н. А. "Двумерное структурное программирование; класс устремлённых графов. (Теоретические изыскания из опыта языка «ДРАКОН»)", здесь:
Ермаков, Жигуненко. Двумерное структурное программирование:
Вложение:
alg_pusk.PNG [ 41.44 КБ | Просмотров: 10166 ]
Алгоритм (рис. 1а) представлен в двух текстовых вариантах (рис. 1б и 1в), добавим третий:
Код:
Пуск = () -> try
OK = Напряжение в норме
Выдать питание на силовую шину
OK = Нагрузка силовой шины в норме
Запуск генератора ВЧ
OK = Параметры сигнала в норме
Пуск системы
catch
Отказ от пуска
В этом коде выражения вида "OK = ..." являются не операторами присваивания, а сопоставление с образцом ("pattern matching" в ФП). Результат функции (например, "Напряжение в норме") мы сопоставляем с атомом/enum (и т.п. конструктором данных) "OK", и если результат иной ("Cancel", "Error" и т.п., согласно декларации функции), то поток выполнения прерывается, и срабатывает обработка ошибок в секции "catch". Транслятор а-ля LiveScript в таких случаях будет генерировать код необязательно через реальные исключения, на выхлопе могут быть те же классические "if"-ы, goto, jump и т.д., что нужно на целевой платформе.
При сопоставлении могут быть переменные, защитные выражения:
Код:
OK x | x > 0 = some_func(...)
# где функция some_func() возвращает кортеж, указывающий или на успех + данные, или на ошибку:
some_func: (...) -> OK data | Error message
# а так мы можем сопоставлять результат функции с содержимым переменной:
OK x | x > 0 = some_func(...) # в "x" сохранили результат
...
^x = other_func(...) # результат ф-ции other_func сопоставляем с результатом предыдущего
# вызова ф-ции some_func
Конечно, управляющий алгоритм выше (насчёт "пуск"-а) - это простой код, собственно, как и исходная Дракон-схема. При множестве исходов работы функций будут добавляться и другие ветки сопоставления результатов (примеры использования "pattern matching" есть на странице LiveScript), что усложнит логику и представление алгоритма, но эффект будет тот же и для Дракон-схемы, причём соизмеримый.
Далее. В рамках снижения ветвления кода можно посмотреть в сторону языка Go, где применяют "отложенные" или "упреждающие" вычисления (не путать с "ленивыми", lazy evaluation, это несколько иное), примерно по таким мотивам:
Код:
load_data = (file_name) ->
OK file = file_open(file_name) # открываем файл
defer file_close(file) # "откладываем" закрытие файла
...
data = load_file(file) # как-то используем файл
...
Здесь "defer file_close(file)" - это вызов встроенной ф-ции defer, где в качестве аргумента передаётся блок кода (лямбда), который будет исполнен позже, после завершения текущего основного блока (всей функции в данном случае). Т.е. закрытие файла выполнится как бы в конце функции, в любом случае даже при вылете по исключению. Причём вызов "defer" явно определяет точку, когда именно "включаются" отложенные вычисления. Т.е. в данном примере если открытие файла не будет успешным (отрицательный результат сопоставления в первой строке ф-ции), то следующая строка (defer) не будет исполнена, соответственно не будет и закрытия файла в дальнейшем.
Такая техника похожа на некие явные управляемые "деструкторы", но позволяет "по месту" указывать необходимые контекстные действия, что нагляднее, чем выносить их в отдельные процедуры. Плюс такой подход уменьшает потребность в реализации библиотечных деструкторов, когда речь идёт о разовых специализированных алгоритмах (и не обязательно в контексте освобождения ресурсов), и нет нужды оформлять код для многократного применения, при этом создавать класс/объект/интерфейс/миксин/класс_типа и т.д. по вкусу, фактически, только для деструктора. С одной стороны, вроде как, на первый взгляд - это серьёзная почва для усложнения кода, но практика показала обратное (благодаря отложенным вычислениям в Go нет дополнительных управляющих инструкций для обработки исключений вида try/except/catch/with/finally и т.п., встречается и критика "defer", но больше именно в контексте имеющихся принципов обработки ошибок, а сами "defer", вроде как, "народ принял").
Что ещё можно подкинуть... Всё больше и больше в императив приходят отработанные техники из ФП, в т.ч. и комбинаторы, т.е. построение алгоритмов из комбинаций функций (и операторов), которые жонглируют блоками кода в качестве аргументов. А вот с наглядными структурными блоками кода у Дракона те же проблемы, что и у блок-схем, особенно когда необходимо локализовать блок "по месту", в контексте текущего алгоритма (как блоки для "defer" выше). Эта особенность затронута, например, здесь:
ДРАКОН и лямбда-выражения.
И, кстати, о примере первичного исходного кода из этой темы:
Код:
# Исходный пример на Erlang:
SelectedValues = lists:map(
fun(key) ->
value = dict:fetch(key, SomeDictionary),
string:to_upper(value)
end,
Keys)
# здесь имеется неэргономичность в том плане, что "лямбда" находится внутри скобок в качестве аргумента
# для map, да ещё за ней следует второй аргумент Keys. Это результат такого объявления ф-ции map:
map: (item -> res, list) -> list
# так удобно для каррирования:
upper_list' = map to_upper(it) # каррируем ф-цию, передаём только лямбду "to_upper(it)"
...
upper_list = upper_list' some_list # применяем для какого-то списка
# для полного вызова удобно вынести лямбду (первый аргумент) за пределы скобок (в данном случае
# получится инвертирование аргументов). В некоторых языках используют для этого, например, "do":
SelectedValues = do lists:map Keys, key ->
value = dict:fetch(key, SomeDictionary)
string:to_upper(value)
Собственно, я лишний раз хочу подчеркнуть то, что эргономичный вид кода важен не менее, чем эргономичная укладка икон в диаграмме, в т.ч. когда этот код вписан в икону.
В общем, я в двух словах хотел показать, что и в рамках текстового представления есть средства для управления сложностью, кроме декомпозиции, причём речь идёт о средствах поближе к "ручному управлению" на месте, тут же, локально, не отходя от кассы (от контекста текущего алгоритма). И ключевой момент не в сравнении визуального и текстового представления (нужны все), основной посыл в том, что явная визуализация потока выполнения имеет предел (причём не такой уж и высокий), до которого дракон-поезд помогает, дальше уже помогает слабо, или не работает вовсе, или ещё больше затрудняет. Поэтому и появляются регулярно темы форума, по мотивам указанных выше ("Преобразование графов", "ДРАКОН и лямбда-выражения" и многие другие). И вполне не беспочвенны желания наблюдать и наглядный "поезд", и наглядную структуру алгоритма, особенно там, где поезд ездить не может. Отсюда и нередки подобные темы:
Альтернативный ВИЗУАЛЬНЫЙ СТРУКТУРНЫЙ ПОДХОД К АЛГОРИТМАМ (интересующимся: там затронуты несколько технологий). И есть попытки совместить драконовские граф-маршруты с декларативными алго-описаниями, например, здесь:
ДРАКОН и функциональные языки программирования.
И, кстати, выкладываю картинку, где пример алгоритма из этой темы, в виде Дракон-схемы, гибридной схемы с элементами FBD и в виде эквивалентного функционального текстового описания:
Вложение:
first_upper.PNG [ 55.61 КБ | Просмотров: 10166 ]
Безотносительно к этому конкретному примеру (очевидно, что это лишь условные примерчики), основной контекст в том, что визуализация только ради визуализации - это не тот способ для выживания (в сфере программирования, во всяком случае).
И добавлю ещё одно сравнение (источник
здесь):
Вложение:
states.PNG [ 25.91 КБ | Просмотров: 10166 ]
Здесь примерчик модели автомата в виде UML-диаграммы и два варианта её текстовой декларации (синтаксис авторский в соответствии с используемой программной платформой). Текстовая форма приведена всего лишь для ремарки: с текстом не всё так уж и плохо, особенно учитывая интерактивные средства для работы с ним. А вот графика...
Есть попытки наработать методологию проектирования автоматов (весьма полезное дело), например, здесь:
ДРАКОН и конечные автоматы. Но, я уверен, что в реальных масштабных моделях автоматов Дракон-диаграммы будут иметь особенности таких схем, как здесь:
Переключатель с очень большим количеством Вариантов. Если задать только модель, без алгоритмики, то однозначно длиннющею "колбасу" (с относительно пустыми или слабо наполненными ветками) необходимо как-то сворачивать, чтобы хоть как-то целостно обозреть суть в замкнутом логическом контуре. Если всё сразу (и полная алгоритмика) - тесные логические связи разлетаются ого-го как. А для автоматов с вложенными состояниями поддадут жару и "кейсы над кейсами", и в результате, будут всё те же страсти, как и в "Преобразование графов".
В общем, всё та же потребность в структурных блоках...
Одним словом, для повышения уровня наглядности рекомендации использовать комментарий как икону-прямоугольник вместо боковой приставки, ибо это есть фигура с замкнутым контуром, да и по ходу маршрута... - явно недостаточно (имхо, те же Р-схемы для задач программирования адаптированы лучше, в функциональном плане...).
С другой стороны, Дракон по-своему хорош именно в таком сегодняшнем виде, без усложнений (относительно), особенно в контексте как алго-бейсика для не IT-специалистов. Но в этом случае я согласен с communicay (и многими другими), что ограниченная применимость Дракона не соответствует агрессивным заявлениям о революционных переворотах...