Website development
15 May 2009

Палка о двух концах или ещё раз о хрупкости кода

Я пишу этот топик, как ответ на недавнюю статью «10 приемов, разрушающих хрупкую красоту кода», в которой разгорелось множество споров.

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

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

То, что Автор рассказал, хорошие советы. Но он показал лишь одну сторону медали. Я сейчас покажу другую.

Объявление всех переменных в начале программы


Я считаю следующее высказывание — абсурдом:
Объявление всех переменных в начале функции — страшное зло

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

В следующем примере, ясно видно, что функция возвращает array():
  1. function sql2array($query, $field=false) {
  2.         $Result = array();
  3.  
  4.         if (DEBUG)
  5.                 print ("Query: $query\n");
  6.                
  7.         $queryResource = $this->query($query);
  8.         if (!$queryResource)
  9.                 return false;
  10.  
  11.         while($Data = $this->fetch_array($queryResource)){
  12.                 if(!$field)
  13.                         $Result[] = $Data;
  14.                 else
  15.                         $Result[$Data[$field]] = $Data;
  16.         }
  17.  
  18.         $this->free_result($queryResource);
  19.  
  20.         return $Result;
  21. }
Если-бы я поставил $Result после проверки !$queryResource, то глазами пришлось-бы бегло перечитывать код в поисках типа этой переменной.
Именно для этого в C все переменные определяются в начале. Вы читаете их, как оглавление (книги) кода, уже предварительно понимая, что в коде будет происходить. Но это тоже не панацея, а лишь другая сторона силы. :)

Переменные, которые используются лишь в вычислениях, циклах, но, например, не возвращаются, можно (и в идеальном случае, даже нужно) объявлять перед вычислениями. (как это сделано, например, с $queryResource).
Вы только представьте: У нас есть функция в 300 строк кода [2]. Где-нибудь на 200-й строке нам надо поменять две переменные местами. Для этого мы лезем на 200 сток выше в начало функции, объявляем переменную temp, которая не имеет никакого отношения ко всей функции, а используется только один раз в одном месте, потом опять возвращаемся к 200-й строке и меняем переменные местами… По-моему, это просто кошмар.
Это не страшно. Когда вы пишите код, вы знаете, что в нём происходит. Страшно становится другим людям, когда они читают код и пытается в 300 строчках найти нужные им переменные.

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

Таким образом, просто думайте что и как вы пишите методы/функции. И делайте почаще Рефакторинг. :)

Возврат результата функции через ее параметр


Опять здесь появляются недосказанности. Автор правильно говорит, что, когда нет необходимости, стоит пользоваться return'ом. Но, необходимость иногда возникает!

Реализация функции, которая возвращает несколько значений разных типов:
  1. function sql2array($query, $field=false, &$error=NO_ERROR) {
  2.         $Result = array();
  3.  
  4.         if (DEBUG)
  5.                 print ("Query: $query\n");
  6.        
  7.         $queryResource = $this->query($query);
  8.         if (!$queryResource){
  9.                 $error = QUERY_ERROR;
  10.                 return false;
  11.         }
  12.  
  13.         while($Data = $this->fetch_array($queryResource)){
  14.                 if(!$field)
  15.                         $Result[] = $Data;
  16.                 elseif (isset($Data[$field]))
  17.                         $Result[$Data[$field]] = $Data;
  18.                 else{
  19.                         $error = FIELD_ERROR;
  20.                         return false;
  21.                 }
  22.         }
  23.        
  24.         $this->free_result($queryResource);
  25.  
  26.         return $Result;
  27. }
Как видите, функция теперь возвращает два параметра — это $error — статус выполнения функции и массив, в случае правильного выполнения.

Но так делать в таких случаях, тоже не панацея. Так как это метод класса — то самым правильным будет — добавить в этот класс методы SetError(errorType) и errorType GetLastError(). И использовать их во всех методах. Практично, удобно и легко реализуемо:
  1. class A{
  2.         private $error;
  3.  
  4.         public function getLastError(){
  5.                 return $this->error;
  6.         }
  7.        
  8.         private function setError($error){
  9.                 $this->error=$error;
  10.         }
  11.        
  12.         public function sql2array($query, $field=false) {
  13.                 $Result = array();
  14.        
  15.                 if (DEBUG)
  16.                         print ("Query: $query\n");
  17.                
  18.                 $queryResource = $this->query($query);
  19.                 if (!$queryResource){
  20.                         $this->setError(QUERY_ERROR);
  21.                         return false;
  22.                 }
  23.        
  24.                 while($Data = $this->fetch_array($queryResource)){
  25.                         if(!$field)
  26.                                 $Result[] = $Data;
  27.                         elseif (isset($Data[$field]))
  28.                                 $Result[$Data[$field]] = $Data;
  29.                         else{
  30.                                 $this->setError(QUERY_ERROR);
  31.                                 return false;
  32.                         }
  33.                 }
  34.                
  35.                 $this->free_result($queryResource);
  36.        
  37.                 return $Result;
  38.         }
  39. }
Вуаля.
Правда теперь нельзя этот код использовать в многопоточных программах (прошлый было можно), но тут всё зависит уже от поставленной задачи. Тем более, комментаторы поправляют меня, что здесь можно использовать для обработки ошибок — исключения, в чём я с ними полностью солидарен.

Здесь мы плавно переходим к примерам, где необходимо использовать доступ к свойствам объекта через object.getProperty () и object.setProperty (value).

Доступ к свойствам объекта


Разберем случай, когда явно будет необходимы доработки метода с проверкой на значение:
  1. class A{
  2.         protected $length;
  3.  
  4.         public function getLength(){
  5.                 return $this->length;
  6.         }
  7. }
  8.  
  9. class B extends A{
  10.         public function getLength(){
  11.                 if ($this->testLength())
  12.                         return ($this->length + 1)* 100;
  13.                        
  14.                 return parent::getLength();
  15.         }
  16. }
Полностью классы я не привожу. Но уже становится ясным, по каким причинам стоит задуматься о том, каким методом пользоваться для изменения переменной. При всём при том, стоит задуматься о том, как ваш класс может использовать в многопоточных приложениях, или например, при работе с БД.

Всё зависит напрямую от архитектуры (и языка программирования). Можно (и нужно) использовать свойства там, где они есть, а методы, где нет свойств. :)
В одних случаях, оправданно использовать public (без лишних перегрузок, когда необходимо просто сохранять/считывать значения структур), в других перегрузку операторов, в третьих — методы воздействия, в четвертых — свойства.

Отсутствие именованных параметров функции


Здесь лагерь разделится на несколько группировок. Любители ООП и любители функционального программирования.

Я лишь покажу, что не везде работают именованные параметры.
В языках без их поддержки — сразу возникают проблемы:
1) Необходимость в ручную проверки всех входящих параметров.
2) Необходимость знания названий и количества этих параметров. (при вызове)
И ещё одна.
Представим, что у нас есть несколько функций. DrawRectangle, DrawCircle, DrawConus, DrawLine, DrawPoly… Список можно продолжать до бесконечности.

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

В процедурном программировании по хорошему счёту — разделяют на «атомарные» функции предварительной инициализации. Например — это реализовано в OpenGL. Но я приведу пример автора:
  1. BackgroundColor (1, 1, 255);
  2.  
  3. BorderColor (255, 1, 1);
  4. BorderWidth (2);
  5.  
  6. DrawRotation (30);
  7.  
  8. AlphaColor (20);
  9.  
  10. DrawRectangle (80, 25, 50, 75);

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

А теперь о минусах подхода автора. Почему не стоит делать, как он говорит (в языках без поддержки именованных параметров):
  1. void DrawRectangle (Rectangle rect){
  2.    Line line;
  3.    float x1, y1;
  4.    float x2, y2;
  5.    
  6.    x1 = rect.x - rect.width/2;
  7.    y1 = rect.y - rect.height/2;
  8.    x2 = rect.x + rect.width/2;
  9.    y2 = rect.y + rect.height/2;
  10.    
  11.    line.color = rect.borderColor;
  12.    line.weigth = rect.borderWeigth;
  13.    line.alpha = rect.alpha;
  14.  
  15.    //rotation и прочее представим, забыли...
  16.  
  17.    line.x1 = x1;
  18.    line.y1 = y1;
  19.    line.x2 = x2;
  20.    line.y2 = y1;
  21.    
  22.    DrawLine(line);
  23.    
  24.    line.x1 = x2;
  25.    line.y1 = y1;
  26.    line.x2 = x2;
  27.    line.y2 = y2;
  28.  
  29.    DrawLine(line);
  30.    
  31.    line.x1 = x2;
  32.    line.y1 = y2;
  33.    line.x2 = x1;
  34.    line.y2 = y2;
  35.  
  36.    DrawLine(line);
  37.    
  38.    line.x1 = x1;
  39.    line.y1 = y2;
  40.    line.x2 = x1;
  41.    line.y2 = y1;
  42.  
  43.    DrawLine(line);
  44. }
Практически, тоже самое произойдёт, если использовать Command паттерн.
Таким образом не все способы хороши. А то, что описывал автор — вовсе не панацея.

Совсем не всегда полезно передавать функции все параметры в ОДНОМ.
Используя процедурное программирование, можно просто передать готовые результаты DrawLine (x1, y1, x2, y2). Выглядеть это будет вот так. (без дополнительных инициализаций Color, Alpha, LineWeight итд)

Про дублирование данных


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

Вместо заключения


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

Используйте обе стороны силы. :)
Успехов. И жду от вас дополняющих комментариев.

+87
1k 41
Comments 91
Top of the day