Nickolay.info. Тексты. 12 самых спорных приёмов программирования

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

1. Большие по размеру кода функции

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

Почему так всё же делают:

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

Действительно детально спроектировать структуру приложения и оптимально разбить его на функции - зачастую гораздо дольше, чем написать это приложение :)

2. Отдельные блоки описания переменных. Описания всех переменных в начале функции

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

Почему так всё же делают:

В некоторых языках - паскалеподобных, иначе просто нельзя.

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

Свобода описания переменных порождает трудноуловимые ошибки, особенно если программист злоупотребляет глобальным контекстом описания (яркий пример - PHP 3-4).

Кроме того, если функция легко бьётся на блоки - скорее всего, она бьётся и на подфункции (см. 1).

3. Длинные списки параметров в функциях

Я не очень верю, что функция с 13 параметрами - это хорошее решение.

Почему так всё же делают:

А как иначе, если, например, генерится рисунок, которому нужно указать размеры, формат, качество и ещё кучу данных? Лепить ещё одну структуру, как любят разработчики си-подобных библиотек? Но чем это проще?

4. Функции, которые возвращают результаты через некоторые свои параметры

Всё равно, через ссылочный тип данных, указатель или что-то ещё. По идее, логическое правило в отношении функций должно быть простейшим: одна функция - один результат, как в математике y=f(x):

результат = функция (аргумент1, аргумент2);

Остальное - от лукавого.

Почему так всё же делают:

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

Можно возвращать структуру или указатель на неё, но её ведь опять нужно где-то описывать (см. 2)?

5. Функции, у которых не именованы параметры. Функции, у которых нет значений параметров по умолчанию

Если вдуматься, двойная проблема:

func ('Вася','Иванов',30000);

А что такое 30000? Но тут 3 параметра, а бывает ведь и 10.

А как хорошо было бы

func (name: 'Вася', surname: 'Иванов', pay: 30000);

или

func ( surname: 'Иванов', name: 'Вася');

а в самой функции все параметры чтоб инициализировались значениями по умолчанию:

function func ( name: '', surname: '', pay: 0) { /* ... */ }

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

Вечное соблюдение этих тройных правил соответствия параметров просто задалбывает.

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

Почему так всё же делают:

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

Если можно будет менять местами параметры - их можно будет и путать.

В объектных языках проблему можно решить через шаблоны.

Лучше всего было бы создавать объект с нужными свойствами прямо "на лету", но это есть разве что в интерпретируемых языках, таких как Javascript.

6. Локальные функции

Если функция используется только другой функцией - ей место внутри первой.

С другой стороны, как быть, если возникает необходимость использовать такую функцию ещё где-нибудь?

7. Лишние вложения условий

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

Почему так всё же делают:

Вложенный сработает быстрее и без лишних проверок :)

8. Данные об одном объекте "разбросаны" по разным контейнерам

Например, в одном массиве - имена, а в другом - фамилии, в цикле идёт паралелльная обработка двух массивов.

Актуально только для процедурных языков, в ООП так обычно не делается.

Очень легко ошибиться, кроме того, никакой логики: по сути дело, неясно, где же моделируемый объект!

Почему так всё же делают:

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

9. Размер массива хранится отдельно от массива

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

Почему так всё же делают:

Во многих языках функции или свойства для получения размера массива просто нет, например, в классическом C/C++.

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

10. Методы, служащие только для обращения к полям объекта

Всякие object.get() и object.set().

Путается работа с данными и функционал, поведение объекта:

object.property=a;

конечно, естественней, чем

object.setproperty(a);

Вся инкапсуляция идёт коту под хвост.

Почему так всё же делают:

А что, если свойство, изменённое нами "напрямую", меняет кто-то ещё? А если нужно при каждом "прямом" присваивании значения свойству сделать дополнительно что-то ещё (стукнуть в налоговую, что на счету добавилось денег)? А если нужно при присваивании провести встроенный контроль на допустимость значения?

11. Рекурсия

Средний программист помнит из институтского курса 2-3 примера рекурсии - факториал, ряд Фиббоначчи и какой-нибудь "путь по лабиринту". О том, что любая рекурсия склонна завершаться на гораздо более глубоком уровне вложений, чем ожидалось, а переполнение стека в программах с рекурсией "вылазит" в самых неожиданных местах, он обычно узнаёт уже на горьком опыте :)

Почему так всё же делают:

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

12. "Прямое" использование системных особенностей того или иного языка

На самом деле, огромная по масштабу проблема.

В старом добром Паскале можно было написать a[0] и это работало как размерность массива (нумерация элементов там с 1).

В код на C++ можно сделать ассемблерную вставку.

В PHP можно "взять" значение многих настроек без ini_get.

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

Почему так всё же делают:

Выигрыш в быстродействии. Учёт особенностей того или иного "железа" или софта. А о портируемости думаешь не очень, когда пишешь программку за деньги :)

Рейтинг@Mail.ru

вверх гостевая; E-mail