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

    Я пишу этот топик, как ответ на недавнюю статью «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) Я лишь добавлю, что он описывает уже этап оптимизации системы.
    При оптимизации — уже работают в первую очередь правила оптимизации, где все средства хороши :) (иногда даже и Денормализация). При всём при том, даже оптимизируя, необходимо следить, чтобы код выглядел читабельно.

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


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

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

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 91

      +5
      Ещё можно добавить по поводу хранения размера массива в отдельной переменной. Согласен, что дублирование данных — это, как правило, плохая практика, но иногда дублирование упрощает код или повышает его эффективность. Если функция работает с массивом и в ней содержится много обращений к длине массива, то проще сохранить длину в отдельной переменной с коротким и понятным именем, например n.

      К примеру, функция, выполняющая слияние двух отсортированных массивов на C#.
      С хранением размера в отдельных переменных:

      static int[] MergeArrays(int[] firstArray, int[] secondArray) 
      {
          int n1 = firstArray.Length, n2 = secondArray.Length;
          int[] res = new int[n1 + n2];
          int p1 = 0, p2 = 0, p = 0;
          while (p1 < n1 || p2 < n2) 
          {
              if (p1 < n1 && (p2 == n2 || firstArray[p1] < secondArray[p2]))
                  res[p++] = firstArray[p1++];
              else
                  res[p++] = secondArray[p2++];
          }
          return res;
      }
      


      Без хранения размера:

      static int[] MergeArrays(int[] firstArray, int[] secondArray) 
      {
          int[] res = new int[firstArray.Length + secondArray.Length];
          int p1 = 0, p2 = 0, p = 0;
          while (p1 < firstArray.Length || p2 < secondArray.Length) 
          {
              if (p1 < firstArray.Length && (p2 == secondArray.Length || firstArray[p1] < secondArray[p2]))
                  res[p++] = firstArray[p1++];
              else
                  res[p++] = secondArray[p2++];
          }
          return res;
      }
      


      Какой вариант легче читается, а следовательно легче поддерживается и меньше шансов допустить ошибку?
      • НЛО прилетело и опубликовало эту надпись здесь
          +2
          Видимо, я не очень удачный пример выбрал. Ну суть в том, что если в коде часто встречается «someArray.Length», то можно заменить его на «n». И читать, и писать код будет удобнее.
            +5
            n очень понятно, ага…
              +4
              блин, ну someArrayLength тогда. Просто вам хотели сказать, что если вы много раз в коде бездумно считываете некоторое свойство, то можно попасть, если это ресурсоемкий геттер. В случае длинны массива не так очевидно, ну а если допустим есть объект со свойством числового типа, а также свойством, содержащим факториал данного числа. :)
                0
                В предыдущем посте как раз были обсуждения геттеров и их ресурсоемкости.

                It depends, короче. Тот же foreach, например, оптимизирует вызов, так что геттер на Length вызывается 1 раз.
                  0
                  > геттер на Length вызывается 1 раз

                  Блин, а мы спорили… Спасибо.
                0
                А почему нет? Точно также, как и в случае с i, j понятно, что это счётчики цикла. Я считаю, что длина имени идентификатора должна быть адекватна его цели. В общем случае длина должна быть пропорциональна области видимисти идентификатора. Локальные переменные в функциях вполне можно называть однобуквенными именами.
                  0
                  ага, а как придется отрефакторить свой же код (который, кстати вообще без комментов, так как делался в перманентной спешке) через полгода придется детально разбираться, что это за такие красивые одно-двух буквенные «штучки». i да j — оно классичненько, для for. Но не панацея.
                +1
                Ну суть в том, что если в коде часто встречается «someArray.Length», то можно заменить его на «n». И читать, и писать код будет удобнее.

                в подавляющем большинстве случаев за вас это сделает компилятор ;)
                  0
                  В данном случае я говорил не про эффективность, а про читабельность кода. Компилятор же не будет менять текст в исходном файле.
                    0
                    применительно к данному примеру согласен. читать и писать удобнее
              0
              Если я правильно понимаю, то первый вариант предпочтительней использовать ещё и потому, что операция получения длинны массива занимает больше времени, чем получение значения переменной. Т.е. первый код должен работать быстрее.
                –2
                >Если функция работает с массивом и в ней содержится много обращений к длине массива, то проще
                >сохранить длину в отдельной переменной с коротким и понятным именем, например n.

                Офигительно понятное имя! :)
                  –1
                  Вы просто не работали с функциональными языками :)
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      По-моему, право на существование имеет только i и то в редких случаях.
                      Неужели так сложно назвать переменную nArrayLen? Польза ведь очевидна — говорить о назначении переменной будет ее имя, а не алгоритм, в котором она используется.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          +2
                          В чём польза длинных имён для локальных переменных функции? Вы думаете, что если счётчик цикла назвать loopCounter вместо i, то от этого повысится читабельность кода? Мне кажется наоборот придётся читать много лишних букв.
                            0
                            Вот и вторая сторона именования. Я тоже, когда пытался всё именовать в локальных функциях «по правилам» заметил, что порой такие вещи ухудшают чтение.

                            По этому, для себя я решил использовать именование таких переменных односложными простыми именами. Потипу name, i, x, result, request итд.
                        –2
                        =)))
                      0
                      Возврат через параметр — это для языков, которые не поддерживают кортежи (tuple). Кортежи как раз для того и придумали, чтобы можно было:

                      def foo():
                      return True, 42

                      fOk, nValue = foo()
                      if fOk:
                        0
                        питон == масс исключения, какие нафиг статусы возвращать? o_O
                          0
                          При чем тут питон? Я про tuple. Они в половине языков реализованы. Исключения, это для контроля ошибок на мой взгляд, оно отдельно.
                            0
                            тоесть псевдокод выше не питон?
                              0
                              Нет, это иллюстрация того, что многие языки позволяют сделать return a, b, c и a, b, c = foo() соответственно. К сожалению, c++ и php без буста так не умеют :(. В php, правда, list construct есть, но с ним страшновато выглядит :)
                                0
                                окей :) меня смутил сильно похожий синтаксис и то что я не знаю ни одного языка, где кортежи звались бы tuples, кроме питона :)
                                  0
                                  Erlang?
                                    0
                                    псевдокод слишком императивен чтобы быть эрлангом
                                      0
                                      Не, я про то что есть еще языки где кортежи называют tuples =)
                                        0
                                        А эрланг слишком императивен, что бы быть функциональным в классическом понимании этого определения.
                                      0
                                      Haskell?
                                        0
                                        Я угадаю эту мелодию с 3х нот? ;)
                                          0
                                          или хаскелем )
                                        0
                                        Ну, если мы говорим о двух параметрах, в C++ есть стандартный std::pair.

                                        std::pair<int, std::string> a = foo(bar);
                                          0
                                          кортеж — фактически — неизменяемый список значений
                                +3
                                В следующем примере, ясно видно, что функция возвращает array():

                                function sql2array($query, $field=false) {
                                $Result = array();


                                function sql2array($query, $field=false) {
                                $Result = null;

                                А в этом примере ясно видно, что функция возвращает null :)

                                Пользуйтесь phpDoc. Оно в разы эффективнее :)

                                Может и еще по нескольким вопросам похоливарил бы, только бесполезно это все :) Мне ближе точка зрения автора предыдущего топика. там я вроде только по 1-2 пунктам имел возражения. И также как и он люблю js :)
                                  +1
                                  Извините, пример просто не совсем удачный я выбрал. То, что я здесь описал, не касается конкретно PHP.

                                  Например, в C/C++ ясно будет видно, что мы возвращаем.
                                    +2
                                    И опять неудачный пример. В С++ мы описываем возвращаемый тип в сигнатуре функции:

                                    int Sum(int a, int b) — как-то так :)
                                      0
                                      Пример и правда не совсем удачный. Но об этом я и хотел сказать.

                                      В PHP я просто стараюсь делать максимально говорящие переменные. И определять в начале явно не null (если они возвращают array()). Просто для того, чтобы было яснее происходящее c первых строчек кода метода/функции.

                                      В некоторых случаях это не работает — тогда уже (как было сказанно ниже), деклорации @return (и другие хелперы) спасут программистов :).
                                        +1
                                        Просто для того, чтобы было яснее происходящее c первых строчек кода метода/функции.

                                        Происходящее должно стать понятным не при просмотре текста функции, а по подсказке редактора кода, там где эту функцию вызывают. Смотреть как объявлены служебные переменные, чтобы понять, что возвратит функция — сори конечно, но это бред.
                                          0
                                          Одно из другого не вытекает, сами понимаете. Если я использую phpDoc, мне ничего не мешает писать понятным языком тело функций.
                                            0
                                            Нет конечно. Но специально выносить декларацию переменных в неудобное место ради неясных целей — это не есть понятный язык для тела функции. Переменная должна быть объявлена там, где это удобно. И если код хорошо задокументирован, то этому вообще ничего не мешает — никакие уловки насчет $result = array();

                                            В общем, я хочу, чтобы вы отказались от своего первого возражения :) Остальные — вполне объяснимы и понятны.
                                    0
                                    Зря вы так. Всякие *Doc это конечно хорошо, но требует дополнительных затрат, а в данном случае у нас код сам себя документирует. ИМХО, это полезно, а в случае VB, например, еще и предохраняет от определенного вида ошибок.
                                      0
                                      а в данном случае у нас код сам себя документирует


                                      Нет. Документированный код — это такой код, в котором без всяких домыслов понятно — что делает этот код. Назвать переменную «Result» и присвоить ей массив в самом начале — это не документация. У типизированных языков — что возвращает функция указывается в коде. У любого нетипизированного языка всегда есть чтото вроде phpDoc(jsDoc), который и помогает нам преодолеть вышеописанные проблемы.
                                        0
                                        Приведенный для пхп пример, согласен, не очень красив.
                                        1) У некоторых типизированных языков есть тип Variant (или что-то типа того), который иногда приходится применять.
                                        2) Поверьте, не у любого)
                                        +1
                                        Это оправданные затраты. Нормальная IDE, типа Eclipse, при наведении на метод будет показывать его описание из PHPDoc, например. И вообще, это полезно: сначала написать комментарий, а потом написать код, т.к. сначала думаешь, а потом делаешь.
                                      +3
                                      Как видите, функция теперь возвращает два параметра — это $error — статус выполнения функции и массив, в случае правильного выполнения.
                                      ============
                                      В таком случае, лучше использовать Exception и его дальнейшую обработку
                                        0
                                        При условии, конечно, если оно доступно в языке/среде разработки
                                          +1
                                          А в php 4.0 вобще можно возвращать «кортеж» в первом из которых статус выполнения функции, а во втором сам результат. И делить его при помощи list…
                                          +3
                                          function sql2array($query, $field=false) {
                                            $Result = array();
                                            if [...] {
                                              return false;
                                            }
                                            [...]
                                            return $Result;
                                          }
                                          </code>
                                          это тоже путь темной стороны. Даже если язык позволяет, функция не должна возвращать значения разных типов в зависимости от пути выполнения - уж лучше кинуть эксепшн.
                                            0
                                            Не соглашусь. Это зависит от архитиктуры.

                                            Граблей этим мы не плодим, и куда приятнее и нагляднее в PHP сравнивать get_length(...) не с -1, а с false.
                                              0
                                              Не знаю такую функцию get_length, но вот count выдает интересные значения, на которые многие новички натыкаются…

                                              <?php
                                              $s = false;
                                              echo count($s);

                                              $s = array();
                                              echo count($s);
                                                0
                                                А я и не говорю про функцию в PHP. Ну а перед тем, как использовать count стоит сначала проверить на false.
                                                1. $result=find(...);
                                                2. if ($result !== false){
                                                3.         //...
                                                4. }
                                            +5
                                            Программирование это уже не как раньше. Десяток языков и кругом задач в 20-30 направлениях. Программирование это уже целая отрасль с огромных количеством языков и направлений.
                                            Было бы странно видеть проектировщика электросетей который спорит с архитектором, как именно ставить размеры на чертежах.
                                            Так и в программирование, под каждое направление определяется свой набор правил. Главное чтобы эти правила было обдуманными и логичными.
                                              0
                                              при сложности возвращаемого значения оборачивайте функцию в обьект.

                                              и пусть результаты будут доступны как свойства обьекта.
                                                0
                                                Главное — не переборщить. Иначе из калькулятора получится монстр
                                                –2
                                                Жизненно, в отличие от :-)
                                                  0
                                                  Come to the dark side… we have cookies!
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                      0
                                                      будто ты не читал гайды про исключения… не везде исключения хорошая практика. Пример функция FileExists с доп. выходным параметром «bool canWriteThisFile».
                                                        +1
                                                        Хмм… Это в каком языке такая конструкция? Функция должна делать только, то как она называется. Если функция проверки существования файла еще показывает, можно ли в него что-то писать это, извиняюсь, говнокод.
                                                        Скорее всего тут необходимо привести пример из C#: bool Int32.TryParse(string s, out int result), вот тут исключения как раз — плохая идея.
                                                          0
                                                          ну я просто пример привел, когда неуместны исключения. Вы подобрали гораздо более хороший и реальный пример.
                                                      +1
                                                      > В следующем примере, ясно видно, что функция возвращает array():
                                                      Надо не объявлять $Result в начале функции а написать толковый комментарий:
                                                      /**
                                                      * Returns query result as array
                                                      *
                                                      * @param string $query
                                                      * @param bool $field
                                                      * @return array;
                                                      */

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

                                                      С предыдущим автором согласен — переменные должны быть там, где они нужны!
                                                        0
                                                        «Доступ к свойствам объекта»… ИМХО должны быть set\get методы, если set подразумевает какую-то логику работы (например ресайз массива или влияние на значение других property или возвратов функций).

                                                        Если логики особой нет, то и городить два метода (в случае поддержки property) не имеет большого смысла…
                                                          0
                                                          ой, сори не туда написал.
                                                          +3
                                                          > Если-бы я поставил $Result после проверки !$queryResource, то глазами пришлось-бы бегло перечитывать код в поисках типа этой переменной.

                                                          Если бы вы добавили в код больше структуры, вот так:

                                                          function sql2array($query, $field=false, &$error=NO_ERROR) {
                                                                   if (DEBUG)
                                                                           print ("Query: $query\n");
                                                          
                                                                   $queryResource = $this->query($query);
                                                                   if (!$queryResource){
                                                                           $error = QUERY_ERROR;
                                                                           return false;
                                                                   }
                                                                   else {
                                                                          $Result = array();
                                                                           while($Data = $this->fetch_array($queryResource)) {
                                                                               if(!$field)
                                                                                   $Result[] = $Data;
                                                                               elseif (isset($Data[$field]))
                                                                                   $Result[$Data[$field]] = $Data;
                                                                               else{
                                                                                   $error = FIELD_ERROR;
                                                                                   return false;
                                                                               }
                                                                         }
                                                                         $this->free_result($queryResource);
                                                                         return $Result;
                                                                  }
                                                          }
                                                          


                                                          сразу бы стало ясно какого типа переменная $Result.

                                                          >Именно для этого в C все переменные определяются в начале.

                                                          Это просто ограничение Си, введенное для пущей простоты реализации компилятора.

                                                          >Возврат результата функции через ее параметр.… Автор правильно говорит, что, когда нет необходимости, стоит пользоваться return'ом. Но, необходимость иногда возникает!

                                                          Для возврата более одного результата можно воспользоваться кортежами или tagged unions. Так как в PHP нет ни того, ни другого, приходится возвращать результат через параметр. Это ограничение PHP, а не фейл структурного программирования.

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

                                                          Тут нужен tagged union, тогда это будет не бред.

                                                          >В функциональном программировании по хорошему счёту — разделяют на «атомарные» функции предварительной инициализации.

                                                          Вы говорите про процедурное программирование. Процедурное != функциональное.
                                                            0
                                                            На счёт tagged unions.
                                                            В PHP можно возвращать любые классы, значения или массивы. При всём, tagged можно проверять c помощью get_type($result). Если класс — то добавив сравнение get_class($result).

                                                            Только это не поможет, если необходимо возвращать несколько значений.

                                                            Более-менее интерестный вариант рассмотрен здесь. Но опять, не универсальный — имеет ряд преимуществ и недостатков.
                                                            0
                                                            Вот, кстати, верно подмечено в исходной статье (я тоже заметил, но как-то комментарий не написал): если автор так борется за красоту кода, то о каких 300 строчках кода в одной функции может идти речь?
                                                              0
                                                              > Как видите, функция теперь возвращает два параметра — это $error — статус выполнения функции и массив, в случае правильного выполнения.

                                                              Пример нехороший, ошибка это побочный эффект, а функция должна возвращать либо сообщение об ошибке, либо результат, а не одно со вторым вместе. (см. tagged/discriminated unions)

                                                              > Но так делать в таких случаях, тоже не панацея. Так как это метод класса — то самым правильным будет — добавить в этот класс методы SetError(errorType) и errorType GetLastError(). И использовать их во всех методах.

                                                              Это неудобно проверять, поэтому непрактично. Либо исключения, либо то, о чем я говорил выше (впрочем, в языках с нужными фичами одно можно выразить через другое). В Smalltalk тоже хорошо сделано, но перенять этот способ в PHP нельзя.

                                                              Тут еще такая фишка: FIELD_ERROR является ошибкой caller'а (вызывающего кода), а не callee (самой функции — с ней все в порядке), поэтому упасть должен именно он (вы ведь сами захотите узнать, откуда пришел вызов, чтобы исправить ошибку).
                                                                0
                                                                Мне вообще не нравится, когда функция может возвращать либо результат, либо ошибку. Ошибка должна обрабатываться общим механизмом исключений, а не отдельно после каждого вызова функции.
                                                                  0
                                                                  > Мне вообще не нравится, когда функция может возвращать либо результат, либо ошибку

                                                                  Почитайте о discriminated unions, я о них говорю. Динамическая типизация PHP позволяет, конечно, возвращать «либо то, либо это, либо вообще что-то с потолка», но это другое совсем, ортогонально к вопросу.
                                                                0
                                                                > Всё зависит напрямую от архитектуры (и языка программирования). Можно (и нужно) использовать свойства там, где они есть, а методы, где нет свойств. :)

                                                                На самом деле это все (методы, свойства, аксессоры, etc.) ограничения языка. Передача сообщений должна быть, причем сообщение должно быть first-class value.

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

                                                                Немного не так. Есть ситуации, когда нужно знать структуру объекта (это просто данные), и есть ситуации, когда от данных нужно абстрагироваться.

                                                                Перегрузка операторов нужна для ad-hoc полиморфизма (специального полиморфизма). Это немного не то, что dynamic dispatch (полиморфизм в понимании ООП), но близко.
                                                                  +1
                                                                  Буду категоричным. Несмотря на то что, да, на меня после этого свалится куча минусов. Статья ужасна.

                                                                  Если-бы я поставил $Result после проверки !$queryResource, то глазами пришлось-бы бегло перечитывать код в поисках типа этой переменной.

                                                                  Совсем непонятно, почему. Лично я думаю, что объяснение здесь высосано из пальца. Впрочем, объяснение в исходной статье тоже хромает, но она хотя бы есть.

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

                                                                  А вот как показывает моя практика, не всегда это возможно делать. Да, я и сам стараюсь делать методы максимально компактными. Но иногда не выходит. Тут надо понимать, что нагородив методов, мы перейдём от спагетти в одном методе к спагетти из кучи непонятных private-методов. Да и не всегда методам удаётся дать внятное название, вот и получается набор методов вида DoMysteryousThing и DoSomeOtherMysteriousThing. Конечно, к ним можно подписать комментарии. Но! Почему бы тогда не написать всё в одном методе, разделив его этими самыми комментариями на логические блоки?

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

                                                                  А вот тут автор вообще приводит абсурдный пример. Нехорошая это практика возвращать массив или bool. Тем более, это не прокатит в статически типизированных языках. А следующий пример — это возврат к тёмным временам C (поморщился). Вообще, если так уж нужно избежать выбрасывания исключения, то идеальное решение — вернуть структуру вроде Error, конкретизированную типом результата метода.

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

                                                                  Мне не становится ясным ничуть. Вообще, своё мнение об уместности использования свойств я оставил в развёрнутом комментарии к исходной статье.

                                                                  В функциональном программировании по хорошему счёту — разделяют на «атомарные» функции предварительной инициализации. Например — это реализовано в OpenGL. Но я приведу пример автора:

                                                                  Вопрос к автору? А вы вообще осознаёте, что такое «функциональное программирование»? И при чём тогда OpenGL и «инициализация»?

                                                                  А теперь о минусах подхода автора. Почему не стоит делать, как он говорит (в языках без поддержки именованных параметров):

                                                                  Из приведённого ниже фрагмента кода вообще не ясно, почему не стоит делать, как он говорит. И, кстати, я бы этот код написал как-то так:
                                                                  void DrawRectangle (RectangleInfo rectInfo)
                                                                  {
                                                                      var polyline = new Polyline();
                                                                      polyline.Points = rectInfo.Rectangle.Corners.Select(pt => pt.ApplyTransformation(rectInfo.Transform));
                                                                      polyline.Close = true;
                                                                      polyline.Pen = rectInfo.Pen;
                                                                      this.DrawPolyLine(polyline);
                                                                  }

                                                                  Да, как видите, не очень удобно. Зато не надо запоминать в какой последовательности надо передавать параметры. Хотя да, IDE зачастую в помощь. А вот читается код всё равно гораздо лучше. Разумеется, всё было бы ещё лучше, если бы были именованные параметры. Да, надо заметить, что обычно рисование происходит через некий «контекст», который хранится либо внутри самого объекта, через который производится рисование, либо передаётся параметром. Обычно этот контекст и содержит информацию о кистях, шрифтах, преобразованиях. Собственно, это то, про что автор хотел сказать в случае с OpenGL. Вот только к функциональному программированию это никак не относится. И с ООП, кстати, вполне себе уживается.
                                                                    0
                                                                    Вообще, в последнем примере можно было написать и так:
                                                                    polyline.Points = rectInfo.Rectangle.Corners;
                                                                    polyline.Transform = rectInfo.Transform
                                                                      0
                                                                      Вы для каждой функции Draw будете писать свой трансформ?
                                                                        0
                                                                        Нет, для каждой функции DrawFoo я буду писать свой FooInfo. И, кстати, автор и сам говорит об ущербности такого подхода. А что поделать?
                                                                          0
                                                                          Что поделать? Программисту — обязательно думать :), а не нестись реализовывать всё одним подходом.
                                                                          Ваш вариант решения красив, но и в то-же время — наворочен.

                                                                          Я предпочитаю не городить огород, особенно где не нужно передавать один и тот же параметр используя его в нескольких места.
                                                                          Ваш код хорош, когда мы RectangleInfo передаём не только в DrawRectangle, а используем где-нибудь ещё. Реализация:
                                                                          1. void DrawRectangle (float x, float y, float length, float width)
                                                                          2. {
                                                                          3.     Rectangle rect;
                                                                          4.  
                                                                          5.     rect.x1 = x - length/2;
                                                                          6.     rect.y1 = y - height/2;
                                                                          7.     rect.x2 = x + length/2;
                                                                          8.     rect.y2 = y + height/2;
                                                                          9.    
                                                                          10.     this.DrawPolyLine(rect.x1, rect.y1);
                                                                          11.     this.DrawPolyLine(rect.x2, rect.y1);
                                                                          12.     this.DrawPolyLine(rect.x2, rect.y2);
                                                                          13.     this.DrawPolyLine(rect.x1, rect.y2);
                                                                          14.     this.ClosePolyLine(this.FILL);
                                                                          15. }

                                                                          1) Присутствует ясность — сколько линий будут рисоваться. Что вообще происходит в функции.
                                                                          2) Не производится лишних действий (и преобразований из пустого в порожнее).
                                                                          3) Нет необходимости писать лишние методы/функции для трансформации.
                                                                            0
                                                                            Задача посложнее — есть 2 координаты, определяющие прямоугольник. Его надо повернуть на угол 45 градусов, отмасштабировать по оси Х в 2 раза (в любую сторону) и нарисовать.

                                                                            Без структур.
                                                                              0
                                                                              Как вариант — добавить в метод два вызова:
                                                                              rect.rotate(this.angle);
                                                                              rect.zoom(this.zoom);
                                                                              которые преобразуют координаты.

                                                                              Почему вы мне запрещаете использовать структуры — я не понимаю.
                                                                                0
                                                                                Становится понятно, зачем нужны структуры и когда.

                                                                                И почему в GDI многие функции доступны в одном варианте вызова (через координаты), а в OpenGL — в другом (через структуры).
                                                                                  0
                                                                                  Я с вами полностью согласен.
                                                                                  В статье я придерживаюсь мнения, что всё зависит от поставленной задачи, и шаблонов «как надо делать» нет.
                                                                                  И тот и другой способы действенны, но не нужно превращать код в фарс (описывая всё по шаблонам) делая лишнее, когда в этом нет необходимости :).
                                                                                    0
                                                                                    Вот такой вот вариант:
                                                                                    1. void DrawRectangle (Rectangle rect)
                                                                                    2. {
                                                                                    3.     this.DrawPolyLine(rect.point1);
                                                                                    4.     this.DrawPolyLine(rect.point2);
                                                                                    5.     this.DrawPolyLine(rect.point3);
                                                                                    6.     this.DrawPolyLine(rect.point4);
                                                                                    7.     this.ClosePolyLine(this.FILL);
                                                                                    8. }
                                                                                    9.  
                                                                                    10. //...
                                                                                    11. Color cl1, cl2;
                                                                                    12. Rectangle rect;
                                                                                    13.  
                                                                                    14. cl1.RGB(255, 1, 1);
                                                                                    15. cl1.Alpha(20);
                                                                                    16. drawer.ForegoundColor(cl1);
                                                                                    17.  
                                                                                    18. cl2.RGBA(1, 1, 255, 255);
                                                                                    19. drawer.FillColor(cl2);
                                                                                    20.  
                                                                                    21. rect.CoordinatesWH(x, y, width, height);
                                                                                    22. rect.Rotate(angle);
                                                                                    23. rect.Zoom(zoom);
                                                                                    24.  
                                                                                    25. drawer.DrawRectangle(rect);
                                                                                  0
                                                                                  Я ошибся. У нас в этом случае будут использоваться 4 координаты. Но смысл, я думаю, ясен.
                                                                        0
                                                                        Объявление всех переменных в начале программы
                                                                        function sql2array($query, $field=false, &$error=NO_ERROR) {
                                                                        $Result = array();
                                                                        $queryResource = $this->query($query);
                                                                        if (!$queryResource){
                                                                        $error = QUERY_ERROR;
                                                                        return false;
                                                                        }


                                                                        Для возврата ошибок сущетсвует станрный механизм try catch exception
                                                                        И приведенный Вами следовало бы переписать следующим образом
                                                                        function sql2array($query, $field=false, &$error=NO_ERROR) {
                                                                        $Result = array();
                                                                        $queryResource = $this->query($query);
                                                                        if (!$queryResource) throw new Query_Error_Exception("Error description", Error_Code);

                                                                          0
                                                                          Это всё ясно.
                                                                          Смысл всей этой статьи — что не всё так линейно в программировании, как описывает Автор прошлого опуса.
                                                                          Пример у меня несовсем удачный, что поделать :)
                                                                          +1
                                                                          >В следующем примере, ясно видно, что функция возвращает array():
                                                                          >function sql2array($query, $field=false) {
                                                                          >    $Result = array();

                                                                          Во-первых по названию функции и так понятно что она возвращает.
                                                                          Во-вторых у вас она возвращает еще и bool, чего не видно ни по названию, ни по объявленной переменной $result.
                                                                          В-третьих — ваша функция может перестать выполняться до использования $result — зачем же под него выделять память в начале?
                                                                            0
                                                                            Пример 1:
                                                                            Автор не правильно понял утверждение.
                                                                            $Result = array(); не являет строго локальной переменной, да и т.к. она возвращается, то область видимости переменной уже будет не локальная.
                                                                            Поэтому пример наоборот показывает правильность определение возвращаемой переменной в самом начале кода. К этому примеру я бы еще добавил, что return должен быть всегда один(за редким исключением).
                                                                            В изначальном утверждение имелось ввиду временные переменные для некоторых блоков функции, или циклов.
                                                                            Пример 2:
                                                                            Я уже лет 5-7 с пхп не имел дело, но какое там многопоточное программирование простите? Да и даже если оно было, то установка функция присвоения далеко не атомарна. Да и вообще тут метод get с локальной переменной, где тут concurrency?
                                                                            Автор правильно подметил, что надо использовать обработку исключений.
                                                                            Пример 3:
                                                                            Тут думаю надо задуматься об ООП и архитектуре, потому что если есть те вопросы, которые там задаются, то зачем вообще тогда городить такой код ??? Да и опять причем тут многопоточность? где тут синхронизации(х3 возможны ли они в пхп ?) и присвоение переменной какого-либо значения не является атомарной во многих языках программирования.
                                                                            пример Про дублирование данных:
                                                                            любой дубликат кода в ООП исходит из плохой архитектуры(даже при оптимизации). только за редким исключением это не так.
                                                                              –2
                                                                              Нет такого понятия в программировании — оптимальное решение. Есть Приемленые решения и НЕ Приемленые решения.
                                                                                0
                                                                                Займитесь лучше делом :)
                                                                              • НЛО прилетело и опубликовало эту надпись здесь

                                                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                                Самое читаемое