Предлагаю читателям «Хабрахабра» перевод публикации «Why Senior Devs Write Dumb Code and How to Spot a Junior From A Mile Away» за авторством Scott Shipp.


Одна из моих любимых цитат на все времена — Брайана Гетца (Brian Goetz), умнейшего чувака из мира Java, одного из авторов «Java Concurrency in Practice», кроме всего прочего. Цитата взята из интервью, опубликованном на сайте Oracle под заголовком «Пишите тупой код» («Write Dumb Code»). Гетца спросили, как писать хорошо работающий код.

Вот что он ответил:

«Часто писать быстрый код на Java означает писать тупой код — код простой, чистый и следующий самым очевидным объектно-ориентированным принципам».

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

Код опытных разработчиков


Если вы, подобно мне, были когда-то джуниором, возможно, помните, как впервые исследовали код сениора и думали: «Я могу так писать. Почему я не сениор?».

Я длительное время пытался писать такой код, и у меня не получалось.

Загадочным в коде сениора было не то, что я не мог его понять, а что я мог его понять моментально. Он был конкретно тупым и казалось, что там должно быть что-то ещё. Я думал: «Где остальное? Как этот код делает всё это?»

С тех пор я изучил названия всех принципов и признаков кода, делающих его тупым: YAGNI, принцип единственной ответственности, DRY, принцип единственного уровня абстракции, слабое зацепление и т. д. И я стал сениором. На самом деле я ненавижу термин «сениор» и называю себя просто разработчиком ПО, но это другая история.

Главный урок, который я извлёк: писать тупой код трудно, но это приносит экспоненциальную выгоду.

Как отличить джуниора за километр


В книге «Refactoring: Improving the Design of Existing Code» Кент Бек говорит:

«Любой дурак может писать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям.»

Вы всегда сможете узнать начинающего разработчика, просматривая код полный «умных» однострочников, сомнительных абстракций и массы специфических языковых конструкций. Я бы сказал, что последнее наиболее характерно. Это выглядит, как-будто код пытается сказать: «Посмотри на меня! Мой создатель отлично знает язык! Я использую локальный потоковый синхронный конструктор копирования JavaBean с интерфейсом по-умолчанию и непроверяемыми исключениями кастомных дженериков вместе с многофункциональным генератором кода JAXB Lombok с усиленной безопасностью!».

Да, я сказал чепуху, потому что в чепуху превращается код в руках тех, кто думает исключительно о компьютерной стороне вещей вместо человеческой.

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

Но код — это человеческий язык. Он объясняет все «кто», «что», «когда», «где», «как» и «почему» задачи, заодно давая инструкции компьютеру. Он должен иметь смысл и пять лет спустя, когда компания будет продана, и новая команда, никогда не видевшая этого кода, откроет его для улучшения и исправления.

Да, написание тупого кода — это трудно. Я убеждаюсь в этом всё больше и больше с течением времени. Я чувствую удовлетворение, когда получаю коменты типа: «Чистый код!» на код-ревью. Я знаю, что лучшая вещь, которую я могу сделать для своей команды и для будущих мейнтейнеров кода — писать тупой код.

Вот мысль, которую приписывают Dave Carhart:

«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».

Комментарии (312)


  1. devalone
    21.01.2018 22:09

    Достаточно тупой код чтоб быть синьёром?

    bool IsBukva(char symbol)
    {
    switch(symbol)
    {
    case'a':return 1;break;
    case'b':return 1;break;
    case'c':return 1;break;
    case'd':return 1;break;
    case'e':return 1;break;
    case'f':return 1;break;
    case'g':return 1;break;
    case'h':return 1;break;
    case'i':return 1;break;
    case'j':return 1;break;
    case'k':return 1;break;
    case'l':return 1;break;
    case'm':return 1;break;
    case'n':return 1;break;
    case'o':return 1;break;
    case'p':return 1;break;
    case'q':return 1;break;
    case'r':return 1;break;
    case's':return 1;break;
    case't':return 1;break;
    case'u':return 1;break;
    case'v':return 1;break;
    case'w':return 1;break;
    case'x':return 1;break;
    case'y':return 1;break;
    case'z':return 1;break;
    case'A':return 1;break;
    case'B':return 1;break;
    case'C':return 1;break;
    case'D':return 1;break;
    case'E':return 1;break;
    case'F':return 1;break;
    case'G':return 1;break;
    case'H':return 1;break;
    case'I':return 1;break;
    case'J':return 1;break;
    case'K':return 1;break;
    case'L':return 1;break;
    case'M':return 1;break;
    case'N':return 1;break;
    case'O':return 1;break;
    case'P':return 1;break;
    case'Q':return 1;break;
    case'R':return 1;break;
    case'S':return 1;break;
    case'T':return 1;break;
    case'U':return 1;break;
    case'V':return 1;break;
    case'W':return 1;break;
    case'X':return 1;break;
    case'Y':return 1;break;
    case'Z':return 1;break;
    default:return 0;
    }
    }
    


    1. VioletGiraffe
      21.01.2018 22:13
      +1

      Нет, недостаточно. Можно ещё тупее:

      if ((symbol >= 'a' && symbol <= 'z') || (symbol >= 'A' && symbol <= 'Z'))

      Речь не о том, что лучше или хуже, но так же реально тупее, если применять это слово к коду, а не к его автору :)


      1. Jenkinse
        21.01.2018 22:56

        Почему этот кусок кода тупой?


        1. Finesse
          22.01.2018 03:19

          Решает задачу наиболее простым образом


          1. ozonar
            22.01.2018 07:02

            В принципе можно ещё подумать и найти ещё более тупой способ.


            1. VolCh
              22.01.2018 09:42

              if (is_alpha(symbol))


              1. laphroaig
                22.01.2018 11:31

                а если symbol=='Ж'? В первом случае такого вопроса просто не возникает, здесь же придется смотреть контекст


                1. iig
                  22.01.2018 11:57

                  А если кодировка отличная от 7-битной? И язык, например, казахский? Не нужно строить лисапедов без необходимости.


        1. MarkGrin
          22.01.2018 16:42

          Стандарт с++ не гарантирует порядок символов английского алфавита в char. Поэтому, чтобы проверить является ли символ буквой нужно пользоваться стандартной функцией std::isalpha.

          bool isBukva (char symbol)
          {
              return std::isalpha(symbol);
          }
          


        1. Asikk
          22.01.2018 16:43

          Потому как легко пропустить одну букву, и функция будет вести себя не так, как ей положено.


      1. Deosis
        22.01.2018 07:28

        Вот только для русской кодировки такой однострочник не работал и пропускал букву Ё.


    1. BasilSnowman
      21.01.2018 22:38

      и не лень было набирать


      1. ZyXI
        22.01.2018 00:27

        <C-r>=map(range(char2nr('a'), char2nr('z')) + range(char2nr('A'), char2nr('Z')), 'printf("case''%s'':return 1;break;", nr2char(v:val))')<CR>


        1. zagayevskiy
          22.01.2018 14:54

          и не лень было набирать


      1. sim31r
        22.01.2018 04:09

        Возможно это вывод в цикле в текстовый файл, программа из трех строчек… а уже написали ))


      1. devalone
        22.01.2018 12:36

        Лень — плохое качество для программиста, говорю тебе как синьёр.


        1. Mikhail_dev
          22.01.2018 16:39
          +4

          Пфф. Лень — двигатель прогресса. Программисту лень что-то делать, и он это дело оптимизирует.


          1. devalone
            22.01.2018 16:40
            -5

            Не спорь с синьёром!


          1. alexeykuzmin0
            22.01.2018 18:03
            +8

            Лень — двигатель прогресса
            Прогресс выводится на орбиту с помощью РН серии Союз-У, двигателями которого являются РД-117, РД-118 и РД-0110.


        1. DEmon_CDXLIV
          22.01.2018 17:02

          habrahabr.ru/post/275841
          Цитата №8.


    1. areht
      21.01.2018 22:56

      Ну какой синьёр, вы букву пропустили


    1. Ostrouschcko
      21.01.2018 22:57

      Теперь я думаю почему индусы не сениоры


    1. mobi
      22.01.2018 00:50

      Возможно, когда-то давно, во времена EBCDIC и других альтернативных (не ASCII) кодировок, это и был тот самый «тупой», зато платформо-независимый код.


      1. AR1ES
        23.01.2018 10:32

        Как показывает моя практика, времена EBCDIC еще не прошли =)


    1. KoCMoHaBT61
      22.01.2018 08:28

      Не тянешь на синьора. Тупизна кода достаточная, но функция должна возвращать bool, а ты возвращаешь int.


      1. fatronix
        22.01.2018 09:51

        В C до C99 не было bool.


        1. mobi
          22.01.2018 10:01

          Тем не менее, в этом случае объявление функции подразумевает, что где-то выше по коду было

          #define bool int
          что в свою очередь, как правило, сопровождается
          #define false 0
          #define true 1

          Поэтому было бы «элегантно» и возвращать true или false.


          1. mobi
            22.01.2018 11:37

            Пришла в голову мысль, что bool может быть объявлен через typedef, а использовать #define запрещено coding style, используемым в компании. Но и тогда, наверное, можно было бы true и false через enum определить.


            1. ZyXI
              22.01.2018 12:59

              Вообще?то bool (как и true и false) по стандарту C99 именно макросы, раскрываемые как _Bool (1 и 0). Стандарт даже разрешает программе переопределять их (правда, сразу объявляет такую возможность устаревшей). Но #define bool int — это что?то сомнительное, sizeof(_Bool) обычно единица, что означает, что вы не сможете с таким определением принимать bool * из библиотек на C99. А при другом соглашении о вызовах или порядке байт и просто bool передавать и принимать также не сможете.


      1. devalone
        22.01.2018 12:30
        +1

        Вообще-то bool, посмотри внимательней


        1. khim
          22.01.2018 18:28

          Это как раз то, о чём говорится в статье. Да, 0 и 1 будет преобразованы в правильный bool — но это как раз шажок на пути к я использую локальный потоковый синхронный конструктор копирования JavaBean с интерфейсом по-умолчанию и непроверяемыми исключениями кастомных дженериков вместе с многофункциональным генератором кода JAXB Lombok с усиленной безопасностью


    1. otvorot2008
      22.01.2018 16:19

      Под такой тип кода есть даже специальный термин: "китайский код" :)


    1. alexeykuzmin0
      22.01.2018 18:00

      Дело не в тупости, а в читаемости. И ваш код для чтения неудобен, по той простой причине, что не умещается на экран.


      1. devalone
        22.01.2018 18:13
        +3

        И ваш код для чтения неудобен, по той простой причине, что не умещается на экран.

        Не проблема
        bool IsBukva(char symbol){switch(symbol){case'a':return 1;break;case'b':return 1;break;case'c':return 1;break;case'd':return 1;break;case'e':return 1;break;case'f':return 1;break;case'g':return 1;break;case'h':return 1;break;case'i':return 1;break;case'j':return 1;break;case'k':return 1;break;case'l':return 1;break;case'm':return 1;break;case'n':return 1;break;case'o':return 1;break;case'p':return 1;break;case'q':return 1;break;case'r':return 1;break;case's':return 1;break;case't':return 1;break;case'u':return 1;break;case'v':return 1;break;case'w':return 1;break;case'x':return 1;break;case'y':return 1;break;case'z':return 1;break;case'A':return 1;break;case'B':return 1;break;case'C':return 1;break;case'D':return 1;break;case'E':return 1;break;case'F':return 1;break;case'G':return 1;break;case'H':return 1;break;case'I':return 1;break;case'J':return 1;break;case'K':return 1;break;case'L':return 1;break;case'M':return 1;break;case'N':return 1;break;case'O':return 1;break;case'P':return 1;break;case'Q':return 1;break;case'R':return 1;break;case'S':return 1;break;case'T':return 1;break;case'U':return 1;break;case'V':return 1;break;case'W':return 1;break;case'X':return 1;break;case'Y':return 1;break;case'Z':return 1;break;default:return 0;}}


        1. alexeykuzmin0
          22.01.2018 18:27
          +1

          Так он в экран уместился, но удобнее для чтения не стал.


        1. vkflare
          25.01.2018 12:18

          Разбор аргументов в gradlew

          case $i in
            (0) set -- ;;
            (1) set -- "$args0" ;;
            (2) set -- "$args0" "$args1" ;;
            (3) set -- "$args0" "$args1" "$args2" ;;
            (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
            (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
            (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
            (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
            (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
            (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
          esac
          


          1. khim
            25.01.2018 12:59

            Ну это ж shell! Какую вы предлагаете альтернативу без bash'измов?


    1. TigerClaw
      22.01.2018 21:15

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


    1. AlexKslv
      25.01.2018 12:18

      Почему мне нравится Питон:

      import string
      symbol = 'a'
      if symbol in string.ascii_letters:
      print(«I'm a letter!»)


  1. michaelkl
    21.01.2018 22:16

    Вот мысль, которую приписывают Dave Carhart:

    Насколько я знаю, это был John F. Wood.


    1. SergeyGalanin Автор
      22.01.2018 08:36

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


    1. sleshdev
      22.01.2018 16:43

      ага, тоже в глаза бросилось


  1. dmitry_dvm
    21.01.2018 22:42

    Я часто применяю тернарные операторы, вместо if else, это тупой код? А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость? По-моему нет, но это и понятно, иначе я бы так не писал.


    1. lair
      21.01.2018 22:55

      А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость?

      Читаемость улучшает, отлаживать сложнее (промежуточные значения не видно).


      1. aidarchikable
        22.01.2018 16:43

        Есть хороший дебаггер для LINQ www.oz-code.com
        Но он платный. Надеюсь в будущих студиях или в решарпер добавят такую функциональность.


    1. webkumo
      22.01.2018 03:00

      Я часто применяю тернарные операторы, вместо if else, это тупой код?

      Нет, это машиночитаемый код. Человеку проще if-else прочитать как бы… Да и в дебаге сильно удобнее, да.


      1. ad1Dima
        22.01.2018 06:03

        Всегда ли проще читать?

        var a = cond ? GetVal1() : getVal2();

        SomeType a = null;
        if(cond)
        {
            a = GetVal1();
        }
        else
        {
            a = getVal2();
        }
        


        Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.


        1. Oxoron
          22.01.2018 14:24

          // Variant 1
          var a = cond 
              ? GetVal1()
              : GetVal2();
          
          // Variant 2
          SomeType a = null;
          if(cond) { a = GetVal1(); }
          else { a = getVal2(); }
          
          // Variant 3
          SomeType a = null;
          if(cond) 
              { a = GetVal1(); }
          else 
              { a = getVal2(); }

          На вкус и цвет…


          1. ad1Dima
            22.01.2018 15:00

            первый всё ещё более читаемый.


          1. sumanai
            22.01.2018 16:01

            На PHP можно даже без скобок

            // Variant 4
            $a = null;
            if($cond)  $a = getVal1(); 
            else $a = getVal2();

            Но это не значит, что нужно так писать. Обычно такое должно регулироваться стайл гайдами.


        1. plMex
          22.01.2018 16:43
          -1

          Теперь я начинаю понимать авторов, которые пишут:

          if (cond) 
          {
            return true;
          }
          else
          {
            resturn false;
          }

          Раньше меня такие конструкции приводили в недоумение…


          1. boblenin
            22.01.2018 18:35

            Им платят за строчки кода. Если бы они просто назвали `cond` читаемым именем не надо было бы городить костыли.


          1. ad1Dima
            22.01.2018 18:52

            не понимаю вашу мысль. Как это связано с моим комментарием?


          1. sand14
            22.01.2018 21:51

            Теперь я начинаю понимать авторов, которые пишут:
            if (cond) 
            {
            return true;
            }
            else
            {
            resturn false;
            }

            Так пишут не для простого кода и читаемости.
            А пишут от непонимания даже базовых типов и операторов.


            Еще часто встречается такой код:


            return cond ? false : true;

            var result = cond ? false : true;

            Или такой:


            var result = someValue == 0 ? true : false;

            И высший пилотаж (скобки на всякий случай для уверенности):


            var result = (someValue == 0 ? true : false);

            Что характерно, эти портянки как раз и объяснялись необходимостью "простого кода, понятного любому разработчику, не знающего тонкости языка".


            Вместо того, чтобы написать:


            return cond;

            return someValue == 0;


            1. michael_vostrikov
              23.01.2018 00:00

              var result = (someValue == 0 ? true : false);

              Я тоже ставлю скобки в таких выражениях. Без скобок при быстром чтении внутренний парсер сбивается. И визуально оно разделяется на 2 части по знаку ==, а не по =.
              someValue = a + b;
              result = someValue == 0 ? result1 : result2;
              // someValue равно a плюс b
              // result равно someValue, ой то есть bool, ой тут тернарный оператор, еще раз, result равно (someValue равняется нулю | да ... нет ...)
              


              1. sand14
                23.01.2018 10:19

                А почему бы тогда так не писать для пущей надежности?:

                var result = ((someValue == 0) ? true : false);


                Впрочем, если мы возвратимся к сути примера, то все гораздо проще:
                var result = someValue == 0;

                Ну или ок, пусть так:
                var result = (someValue == 0);

                В любом случае, все ясно и кратко, и можно со скобками, можно без.

                А то ведь можно дойти и до такого (почему нет)?:
                var result = ((someValue == 0) ? (true ? true: false ) : (false ? false : true));


                1. michael_vostrikov
                  23.01.2018 12:02

                  А почему бы тогда так не писать для пущей надежности?
                  Тут необязательно, так как в тернарном операторе первое условие всегда логическое. Хотя если условие будет длинное, то лучше поставить.

                  А то ведь можно дойти и до такого (почему нет)?
                  Потому что не повышает читаемость.

                  В любом случае, все ясно и кратко, и можно со скобками, можно без.
                  Согласен. С другой стороны, это отвязка от конкретно булевского типа, так проще рефакторить. Например, мы в новом коде сделали true/false, но не уверены, возможно потом будет лучше сделать 0/1/-1. Так мы можем просто заменить значения в ветках на любой другой тип. Но это редко когда нужно.


            1. ad1Dima
              23.01.2018 06:01

              Ну, у меня например не return, так что…

              Для не обрывающегося случая я всегда ставлю скобки, даже если там 1 оператор. Потому это однозначно, и не зависит от отступов. Был нет так давно весёлый баг в популярном опенсорсе:
              if (cond)
                SomeFunc();
              + SomeOtherFuncThatMusBeInIFBlock();

              OtherMethods();


            1. VolCh
              23.01.2018 10:16

              return cond ? false : true; и return cond; в целом неравнозначны :) А если имелось в виду return cond ? true : false;, то в некоторых языках и они не равнозначны, если cond не строго булевого типа, а лишь в некоторых случаях типа if (func()) неявно приводится к нему. Даже если типа bool вообще нет, как в C (или уже есть?) и где-то в языке заложено #define true 1, то return cond; в случае когда cond может отказаться равно, например, 2, может приводить к сложно диагностируемым ошибкам, если клиент func() ожидает строго 1 или 0 в возврате.


              1. sand14
                23.01.2018 10:29

                Это опечатка, имелось в виду, конечно, return cond? true: false.
                Если результат нужно инвертировать, то тернарный оператор также не нужен, достаточно написать return !cond.


                Это я написал про C# и код на нем, который часто доводится видеть.


                Что касается других языков, того же C, то там нужно смотреть какие конкретно нужны значения, и их и возвращать.
                (Обычно там всегда 0 для false, а для true есть варианты, обычно используются разные подходы в зависимости от 8/16/32-разрядности, либо 1, либо "не ноль", либо минус 1.)
                И бездумность применения операторов, в отличие от C#, кроме некрасивого и избыточного кода, принесет и реальные ошибки.


                1. ZyXI
                  23.01.2018 10:38

                  Если можно получить bool унарным !, то не логичнее ли просто два раза инвертировать, если инвертировать не нужно, а bool нужен?


                  1. sand14
                    23.01.2018 10:47

                    Верно, я про это и пишу. Когда видишь код:


                    return cond? true: false;

                    то хочется предложить еще варианты:


                    return !!cond;


                    1. VolCh
                      23.01.2018 11:13

                      А вот такой вариант приведения к bool уже воспринимается менее однозначно чем cond ? true : false в общем случае, вплоть до допускания возможности, что транслятор просто проигнорирует двойное отрицание в целях оптимизации. Тут уже нужно в доки языка лезть.


                      1. sand14
                        23.01.2018 11:28
                        +1

                        Так в том то и дело, что C#, о котором изначально шла речь, cond? true: false не выполняет приведения к bool.
                        cond — уже(!) bool.
                        Тернарный оператор в C# предназначен для возвращения одного из двух значений какого-либо другого типа, отличного от bool (а приведение он не умеет делать — попробуйте поиграться хотя бы с Nulllable/NotNullable/null).
                        Если он возвращает bool — это избыточный код, который как минимум замусоривает код, и хорошо еще, если компилятор это убирает.


                        И да, доки читать нужно. Т.к. в любом языке все операторы и ключевые слова выглядят примерно одинаково (да и возможный набор моделей более-менее одинаков — процедурная/ОО/ФП), а значить могут весьма разное — если смотреть как именно это работает, а не по верхам.


                        1. ad1Dima
                          23.01.2018 12:12

                          и хорошо еще, если компилятор это убирает.
                          убирает


                        1. VolCh
                          23.01.2018 12:51

                          Ну, если cond гарантированно является bool и, желательно, это видно на одном экране с тернарником, то, да, излишний код cond? true: false, но !!cond вообще сбивает с толку в таком случае, наталкивает на мысли об ошибке, что имелось в виду одинарное отрицание.


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


                          1. sand14
                            23.01.2018 13:45

                            В C#, Java (и, предполагаю, в любом современном статически типизированном языке, кроме C) в выражении cond? A: B первый операнд (cond) всегда является bool, иначе код не скомпилируется.


                            И это не относится к тем нюансам языка, которые могут запутать даже опытного разработчика.
                            Это самые базовые вещи.


                            Пытаться усложнять этот код, чтобы он был якобы более читаемым, это все равно что пытаться как-то усложнить запись вызова оператора умножения чисел ("а вдруг у нас гетерогенная разработка, и этот код прочитает программист Руби, а в Руби можно умножать строку на число — давайте напишем вызов оператора умножения, чтоб явно было видно, что мы перемножаем числа").


                            1. alexeykuzmin0
                              23.01.2018 14:13

                              В C#, Java (и, предполагаю, в любом современном статически типизированном языке, кроме C)
                              Потому что в C# и Java более строгая типизация, чем в С. Это о современности особо не говорит.


              1. ZyXI
                23.01.2018 10:34

                bool в C уже давно есть, и приведение к нему, явное или неявное, может выдать только true или false. Хотя вы всё ещё можете получить сложнодиагностируемую ошибку, если будете заполнять bool * какими?нибудь двойками через memset() или любым другим из кучи обходных путей — минимально адресуемая единица памяти до сих пор байт и один bool (если он не битовое поле в структуре) будет занимать именно его.


                1. sand14
                  23.01.2018 10:52

                  Хотя вы всё ещё можете получить сложнодиагностируемую ошибку, если будете заполнять bool * какими?нибудь двойками через memset() или любым другим из кучи обходных путей — минимально адресуемая единица памяти до сих пор байт и один bool (если он не битовое поле в структуре) будет занимать именно его.

                  Даже в C# такое возможно — заполнять bool произвольным однобайтным значением, пометив код как unsafe, либо использовать структуры и FieldOffset из средств маршаллинга данных в/из неуправляемого кода.
                  В последнем случае не потребуется помечать код как unsafe и, соответственно, код не потребует привилегий при исполнения.
                  А поведение при работе с такой булевой переменной будет с ошибками, и еще будет зависеть от версии компилятора.


              1. alexeykuzmin0
                23.01.2018 14:07
                +1

                Даже если типа bool вообще нет, как в C (или уже есть?) и где-то в языке заложено #define true 1, то return cond; в случае когда cond может отказаться равно, например, 2, может приводить к сложно диагностируемым ошибкам
                Можно использовать код
                return !!cond;
                В boost это очень распространено.


            1. alexeykuzmin0
              23.01.2018 13:55

              if (cond) 
              {
                  return true;
              }
              else
              {
                  return false;
              }
              Так пишут не для простого кода и читаемости.
              А пишут от непонимания даже базовых типов и операторов.
              Не всегда. Я, например, так пишу для того, чтобы при дебаге можно было поставить breakpoint на конкретную строчку на конкретное условие. Обычно, правда, в репозиторий это не идет, если ушло — значит, не заметил, поправлю на review.


              1. sand14
                23.01.2018 14:32

                Естественно, должно быть без фанатизма в плане краткости кода.
                Иногда есть смысл в коде с таким ветвлением (хотя в случае дебага можно воспользоваться точкой останова с условием).
                Бывает еще, когда ветвление более не только более читаемо, но и более масштабируемо — в том смысле, что может ожидаться, что блоки будут расширены дополнительным кодом.


                1. alexeykuzmin0
                  23.01.2018 18:21

                  в случае дебага можно воспользоваться точкой останова с условием
                  В VC++, к сожалению, скорость дебага от этого падает в несколько раз.


        1. reddot
          22.01.2018 16:49
          -1

          Строго говоря — этот код не идентичен. В случае тернарного оператора в любом случае будут вычислены оба выражения.


          1. Chaos_Optima
            22.01.2018 16:55
            +1

            С чего это вдруг? Также будет вычисляться лишь одно. ideone.com/hy39CZ


          1. ad1Dima
            22.01.2018 18:51

            1. ad1Dima
              22.01.2018 19:02

              Про свифт и тренарный оператор обсуждалось тут habrahabr.ru/post/316986


        1. alexeykuzmin0
          22.01.2018 18:09

          Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку
          От языка зависит. В плюсах — нет, не позволяет.


          1. ad1Dima
            22.01.2018 18:53

            В начале треда говорится про Linq. В c# позволяет.


        1. webkumo
          22.01.2018 20:35

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


          PS всегда был приверженцем сокращённо-выделенного вида:


          SomeType a;
          if (condition) {
            a = getVal1();
          } else {
            a = getVal2();
          }

          PPS


          Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.

          на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?


          1. ad1Dima
            23.01.2018 06:05

            бы в 5 строчек размером — он резко теряется.
            Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.

            на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?

            F9 (мышкой, наверное, нельзя не уверен)


            1. webkumo
              23.01.2018 12:01

              Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.

              Ну вот порефакторили имя переменной и стало грустно читать. А кому-то и изначально было грустно читать. В общем как я написал — тернарник хорош в случаях когда он получается коротенький-маленький. В остальных случаях его использовать опасно/вредно для читабильности кода.


      1. esef
        22.01.2018 16:43

        все дело в привычке. Для простых условий, как по мне, намного проще прочитать однострочный тенарный оператор чем продиратся через четыре строчки if-else
        для меня такой вариант намного понятнее

        ClosesType closes = isColdOutside ? COAT : TSHIRT

        чем
        ClosesType closes;
        if (isColdOutside)
            closes = COAT
        else
            closes = TSHIRT 
        


        1. cl0ne
          22.01.2018 19:44

          Clothes, вероятно


        1. ad1Dima
          23.01.2018 06:06

          Мне не жалко конечно, но habrahabr.ru/post/347166/?reply_to=10629362#comment_10627954


    1. areht
      22.01.2018 06:59

      Там семантика (обычно) разная: LINQ — query, foreach — command.

      Осмысленный выбор читаемость улучшает.


    1. andreysmind
      22.01.2018 10:44

      Работал много лет назад с одним парнем. Он писал вот так:

      subCategoryName = subCategoryName == 'Pets and animals' ? 'Animals' : subCategoryName == 'Food and wine' ? 'Food/wine' : subCategoryName == 'Opinions and philosophy' ? 'Opinions' : subCategoryName == 'Health and wellness' ? 'Wellness' : subCategoryName == 'Design and architecture' ? 'Design' : subCategoryName;



      1. mclander
        22.01.2018 14:43

        О! Так это же я был))) Не то чтобы часто, но использую конструкцию.

        Кстати, если разбить по строкам читается на удивление хорошо. Прув:

        subCategoryName = subCategoryName == 'Pets and animals' ? 'Animals' : 
                          subCategoryName == 'Food and wine' ? 'Food/wine' : 
                          subCategoryName == 'Opinions and philosophy' ? 'Opinions' : 
                          subCategoryName == 'Health and wellness' ? 'Wellness' : 
                          subCategoryName == 'Design and architecture' ? 'Design' : 
                          subCategoryName;
        


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


        1. Ogra
          22.01.2018 14:57

          Не заметить в этой простыне = вместо == и искать потом баги — бесценный опыт. Так и становятся сеньорами =)


          1. boblenin
            22.01.2018 18:39

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


            1. sumanai
              22.01.2018 18:44
              +1

              Сеньором он становится будет в другом месте.

              И ведь станет!


              1. mclander
                24.01.2018 18:11

                true story — много таких)


        1. andreysmind
          22.01.2018 15:03
          +1

          Он именно в строку писал. Этот код у меня хранится специально для аргументов про тернарные операторы. :)
          Да, так намного лучше.


          1. mclander
            24.01.2018 18:10
            +1

            За написание тернарников в одну строку и без скобок надо сразу лишать печенек.

            Тернарная запись, если не читается однозначно — это источник батхерта, особенно при отладке. Не говоря о отладке чужих багов;)


        1. mobi
          22.01.2018 15:31

          А почему бы не завести какой-нибудь Map<String,String> для такого дела? (не знаю, на каком языке этот код написан; сравнение строк по значению накладывает ряд ограничений, но вариантов все-равно больше одного).


          1. mclander
            24.01.2018 18:07

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

            Кроме того условие может быть немного нестандартным, например с регэкспом.

            Ессно, в какой-то момент такую вещь феншуизируешь… Или нет.

            Но, тем не менее, такой код читается. И часто лучше, чем map ;)


      1. Gryphon88
        22.01.2018 15:01

        можно и так, если грамотно форматировать. Пример из книги «Анализ программного кода на примере проектов Open Source», стр 69

        op = 
        &(
                 !y ? (!x ?  upleft :  x != last ?   upper :   upright ) :
        y != bottom ? (!x ?    left :  x != last ?  normal :     right ) :
                      (!x ? lowleft :  x != last ?   lower :  lowright )
        ) [w->orientation]

        В ёлочку код не всегда читабельнее


        1. midday
          22.01.2018 15:48

          Какая — то фигня


        1. sumanai
          22.01.2018 16:03

          можно и так, если грамотно форматировать.

          А потом из-за изменения длины одной переменной или константы меняем всё выражение.


          1. VolCh
            22.01.2018 16:10

            И хорошо, если успели заметить, что код потёк.


          1. Free_ze
            22.01.2018 16:34

            Наглядность кода стоит усилий.


      1. Firik67
        22.01.2018 16:43

        И где он теперь?


      1. me21
        22.01.2018 16:44

        Ну, если после двоеточия переносить проверку следующего условия на новую строку под предыдущей проверкой, то тоже ничего читается.


      1. DistortNeo
        22.01.2018 17:23

        Вот нашёл такой кусочек кода (строка длиной 2834 символа) в проекте:

        bool specChanged = cur.SpecificNodesParameters.Nodes != prev.SpecificNodesParameters.Nodes || (cur.SpecificNodesParameters.Nodes && (cur.SpecificNodesParameters.Objects != prev.SpecificNodesParameters.Objects || (cur.SpecificNodesParameters.Objects && (cur.SpecificNodesParameters.Attributes != prev.SpecificNodesParameters.Attributes || (cur.SpecificNodesParameters.Attributes && (cur.SpecificNodesParameters.AttributeData != prev.SpecificNodesParameters.AttributeData || cur.SpecificNodesParameters.AttributeInfo != prev.SpecificNodesParameters.AttributeInfo)) || cur.SpecificNodesParameters.Measurements != prev.SpecificNodesParameters.Measurements || (cur.SpecificNodesParameters.Measurements && (cur.SpecificNodesParameters.MeasurementData != prev.SpecificNodesParameters.MeasurementData || cur.SpecificNodesParameters.MeasurementInfo != prev.SpecificNodesParameters.MeasurementInfo)) || cur.SpecificNodesParameters.OperativeMeasurements != prev.SpecificNodesParameters.OperativeMeasurements || (cur.SpecificNodesParameters.OperativeMeasurements && (cur.SpecificNodesParameters.OperativeMeasurementData != prev.SpecificNodesParameters.OperativeMeasurementData || cur.SpecificNodesParameters.OperativeMeasurementInfo != prev.SpecificNodesParameters.OperativeMeasurementInfo)) || cur.SpecificNodesParameters.MonitoringValues != prev.SpecificNodesParameters.MonitoringValues || (cur.SpecificNodesParameters.MonitoringValues && (cur.SpecificNodesParameters.MonitoringValueData != prev.SpecificNodesParameters.MonitoringValueData)) || cur.SpecificNodesParameters.UserValues != prev.SpecificNodesParameters.UserValues || cur.SpecificNodesParameters.Events != prev.SpecificNodesParameters.Events || (cur.SpecificNodesParameters.Events && (cur.SpecificNodesParameters.EventData != prev.SpecificNodesParameters.EventData || cur.SpecificNodesParameters.EventInfo != prev.SpecificNodesParameters.EventInfo)) || cur.SpecificNodesParameters.ObjectInfo != prev.SpecificNodesParameters.ObjectInfo)) || cur.SpecificNodesParameters.Query != prev.SpecificNodesParameters.Query || (cur.SpecificNodesParameters.Query && (cur.SpecificNodesParameters.Response != prev.SpecificNodesParameters.Response || (cur.SpecificNodesParameters.Response && (cur.SpecificNodesParameters.NodeQueryResponseData != prev.SpecificNodesParameters.NodeQueryResponseData)) || cur.SpecificNodesParameters.NodeQueryStatusData != prev.SpecificNodesParameters.NodeQueryStatusData)) || cur.SpecificNodesParameters.NodeConfigData != prev.SpecificNodesParameters.NodeConfigData || cur.SpecificNodesParameters.NodeConnectionData != prev.SpecificNodesParameters.NodeConnectionData || cur.SpecificNodesParameters.NodeStatusData != prev.SpecificNodesParameters.NodeStatusData || cur.SpecificNodesParameters.NodeInfo != prev.SpecificNodesParameters.NodeInfo));


        P.S. Беспокоиться не стоит — это автосгенерённый код. Если выровнять — будет около полсотни вполне красивых строчек.


        1. LynXzp
          23.01.2018 16:40

          P.S. Беспокоиться не стоит — это автосгенерённый код.
          Я долго не мог понять почему мне новенький разработчик жаловался. Оказалось он читал .min.js


          1. sumanai
            23.01.2018 17:25

            Было бы хуже, если бы он в этом файле разобрался и прислал патч.


    1. Germanets
      22.01.2018 13:28

      Частенько замена foreach на LINQ действительно ухудшает читаемость, за этим надо внимательно следить…


      1. boblenin
        22.01.2018 18:40

        Частенько и наоборот.


  1. Alex_ME
    21.01.2018 22:55

    Почему-то мне казалось (из собственного опыта), что быстрый, эффективный и чистый, красивый код зачастую вещи противоположенные и приходится соблюдать баланс. Ну хоть бы взять пример выше — LINQ позволяет сделать код куда компактнее, чище, проще для понимания, но ухудшает эффективность. Или иммутабельность, или различные паттерны проектирования. Поэтому приходится смотреть по ситуации и соблюдать баланс.


    P.S. Если кто не понял — я не за преждевременную оптимизацию и нечитаемые полотна


    1. lain8dono
      21.01.2018 23:43

      Это кстати не про все ЯП. В том же rust функциональщина как минимум так же быстра, как императивщина. Иногда чуточку быстрее. Впрочем rust изначально несёт в себе бо?льшую сложность.


      1. Alex_ME
        21.01.2018 23:50

        Ну тут не только функциональщина же. Вот буквально свежий пример. Делал сему в универе — имитационное моделирование отказов сети. Ну, значит есть граф. И там есть еще всякие параметры служебные для симуляции — исправен ли узел, сколько раз встречается в путях и т.п. По принципу единственной ответственности пихать это в сам граф, который описывает структуру сети, не камильфо. Поэтому перед симуляцией переводилась эта структур в другую, более оптимизированную (ну и ряд других изменений). Добавь я эти поля в граф — было бы менее красиво, но более эффективно.


        1. lain8dono
          22.01.2018 01:10

          Для начала как хранятся данные о связях? Какие части нам требуется оптимизировать? https://en.wikipedia.org/wiki/Graph_%28abstract_data_type%29 (вопросы чисто риторические)


          Добавь я эти поля в граф — было бы менее красиво

          Я не силён в java, но там нет выбора между хранением по ссылке/по значению?


          Всмысле

          чем отличается


          struct Foo {
            a: i32,
            b: bool,
            c: String,
          }
          
          struct Bar {
            n: isize,
            foo: Foo,
          }

          От


          struct Baz {
            n: isize,
            a: i32,
            b: bool,
            c: String,
          }


          1. Alex_ME
            23.01.2018 02:25

            Данные о связях хранятся по-сути в списках смежности.


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


            Например есть параметр "важности" узла, ух, не знаю правильный термин.


            Попытка объяснить

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


          1. khim
            23.01.2018 04:03

            Я не силён в java, но там нет выбора между хранением по ссылке/по значению?
            Нет. Несколько примитивных типов (фиксированный список) передаются по значению, остальные — по ссылке.

            Одна из вещей, который в C# поправили, когда это была просто «улучшенная Java от Microsoft» (в дальнейшем дорожки двух языков разошлись и современный C# и Java отличаются весьма сильно).


            1. myrslok
              23.01.2018 14:52

              Всё передается по значению, но для ссылочных типов значение является ссылкой. Это не то же самое, что передача по ссылке. Например, нельзя написать функцию, которая меняет местами аргументы (в отличие от, скажем, C++, где есть передача по ссылке).


              1. khim
                23.01.2018 14:58
                -1

                в отличие от, скажем, C++, где есть передача по ссылке.
                Это такой очень философский вопрос. Ассемблерный код для «передачи по ссылке» и «по значению через указатель» одинаковы.

                Например, нельзя написать функцию, которая меняет местами аргументы
                Объясните, пожалуйста, пожалуйста, что вы имеете в виду. Передача по ссылке в C++ даёт вам те же возможности, что и обычная передача для больгинства типов в Java. Вот только передать ссылку на int в Java нельзя — отсюда костыли…


                1. myrslok
                  23.01.2018 15:08
                  +2

                  Вы заблуждаетесь.


                  Пример:


                  String foo = "foo";
                  String bar = "bar";
                  swap(foo, bar);
                  // здесь foo приняло значение "bar", а bar" приняло значение "foo".

                  В языке, где есть передача по ссылке, функцию swap написать можно, а в Java — нельзя.


                  1. lain8dono
                    23.01.2018 21:25

                    Кстати, на правах рекламы, в rust это делается несколькими вариантами:


                    let (y, x) = (x, y); // фактически меняет только их имена
                                         // но мы можем так делать с разными типами
                    
                    // Или что-то такое
                    use std::mem::swap;
                    swap(&mut x, &mut y); // но типы должны быть одинаковыми
                    swap(x, y); // если x и y сами по себе ссылки
                    // при этом считается, что ссылки не перекрываются во имя оптимизаций конечно же

                    Доки на swap


                    Плюс там есть replace


                    Если нам нужно сделать сильное колдунство (например с перекрытием памяти у этих штук), то к нашим услугам такие же, но небезопасные ф-ии из модуля ptr.


                    1. khim
                      24.01.2018 13:19
                      -1

                      Если нам нужно сделать сильное колдунство (например с перекрытием памяти у этих штук), то к нашим услугам такие же, но небезопасные ф-ии из модуля ptr.
                      Что, собственно, здесь и потребуется. Причём кончится это может тем, что вся ваша программа рассыпется к чертям собачьим.

                      Проблема не в перекрытии памяти, а в том, что строки в Java — immutable. Соответственно вам нужно будет в swap превратить неизменяемую ссылку на обьект в изменяемую. Да, это можно сделать через unsafe — но сольёт всю «прекрасную» безопасность rustа в унитаз. Или я ошибаюсь?

                      Тот факт, что в Java swap нереализуем, в rust — требует выхода за пределы «безопасного» подмножества языка, а в C++ — всего лишь пары castов — можно обсуждать… но никакого отношения к способу передачи (по ссылке или значению) это отношения не имеет.

                      Передайте в вашу функцию swap в Java вместо String (который менять нельзя) ArrayList (который менять таки можно) — и ваш swap преотлично реализуется…


                      1. myrslok
                        24.01.2018 13:51

                        Передайте в вашу функцию swap в Java вместо String (который менять нельзя) ArrayList (который менять таки можно) — и ваш swap преотлично реализуется…

                        Промутировать аргументы — это не то же самое, что поменять их местами. Имелась в виду тождественость ссылок после вызова swap, а не равенство по equals, разумеется.


                        1. khim
                          24.01.2018 14:35

                          Имелась в виду тождественость ссылок после вызова swap, а не равенство по equals, разумеется.


                          Ну, то есть, вы хотите следующего:
                          String foo = "foo";
                          String bar = "bar";
                          std::cout << "foo: " << &foo << "bar: " << &bar << "\n";
                          swap(foo, bar);
                          //  foo приняло значение "bar", а bar" приняло значение "foo".
                          // Потому следующая строка выведет то же, что и предыдущая.
                          std::cout << "foo: " << &bar << "bar: " << &foo << "\n";
                          
                          Дерзайте.

                          Проверить «тождественность ссылок» в Java нельзя… но в C++-то можно! И видно, что ни о какой «тождественности ссылок» речи не идёт.


                          1. myrslok
                            24.01.2018 15:06

                            Проверить «тождественность ссылок» в Java нельзя…

                            Мне даже немного неловко это писать. Но как это нельзя? А что == делает?


                            На sdt:swap вам уже указали и примеры привели. Честно говоря, мне кажется, всего этого должно быть достаточно, чтобы увидеть свою неправоту.


                            Демонстрация swap со строками
                            // Example program
                            #include <iostream>
                            #include <string>
                            
                            int main()
                            {
                                std::string foo = "foo";
                                std::string bar = "bar";
                                std::cout << "foo: " << foo << " bar: " << bar << "\n";
                                swap(foo, bar);
                                std::cout << "foo: " << foo << " bar: " << bar << "\n";
                            }


                            1. khim
                              24.01.2018 17:12

                              Мне даже немного неловко это писать. Но как это нельзя? А что == делает?
                              Она сравнивает содержимое ссылок.

                              Мутабельность std::string при этом ни при чем, потому что она не используется.
                              Вы это серьёзно?

                              Код std::swap вы, кстати, примели неправильный. На самом деле используется std::swap<char, std::char_traits<char>, std::allocator<char>>, но даже если вы специализации не было… Что вторая строчка в приведённой вами функции делает?


                      1. lain8dono
                        24.01.2018 17:18

                        Вы недостаточно знаете rust, чтоб судить о его недостатках и попросту заблуждаетесь. О текущих недостатках оного лучше читать что-то вроде https://github.com/rust-lang/rfcs/tree/master/text (некоторое, что там есть уже исправлено). Но это после чтения доков, спеков и относительно плотного знакомства.


                        Причём кончится это может тем, что вся ваша программа рассыпется к чертям собачьим.

                        При неаккуратном использовании. Как и в плюсах. Только в rust ещё и пометочка будет, что тут идёт сильное колдунство, о чём кстати и в документации есть. И без оной не скомпиляется.
                        Из документации: “trust me, I know what I’m doing.”. И нужно подобное редко.


                        Соответственно вам нужно будет в swap превратить неизменяемую ссылку на обьект в изменяемую.
                        Или я ошибаюсь?

                        Если нам требуется делать что-то тёмное с неизменяемыми штуками — у нас скорее всего серьёзная ошибка в дизайне. Ах, да. Строки rust не имеют ни какого отношения к строкам в java.
                        Или при реализации самого swap? Ну так то разумеется. А как иначе? Но это ведь часть стандартной библиотеки. Оттестировано и проверено.


                        Ещё по поводу строк в rust. Строка это тип str. Или тип &str, который есть ссылка на неизменяемый по размеру срез массива байт, который находится где-то в памяти плюс некоторое количество валидации для utf-8? Или который всё же &mut str, где мы можем изменять содержимое? А размер нам нужно менять? Тогда String, который в динамической памяти живёт. Сложно, да? Зато работает хорошо. Есть мутабельные и немутабельные для разных контекстов. Документация


                        в rust — требует выхода за пределы «безопасного» подмножества языка

                        Чем читаем? В rust есть безопасный швап и для чорной магии. Если нам просто два значения поменять местами — тот, что в mem используем. Если нужно что-то сложнее — это уже априорно чорная магия, которая требует аккуратности. И в этом случае unsafe будет подсказкой, что эту часть кода надо гораздо внимательнее писать и поддерживать.


                        Алсо о том, как контейнеры/ссылки себя в памяти ведут, есть такая штука https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit (на синенький текст ссылочки на документацию скастовали)


                        1. khim
                          24.01.2018 18:07

                          Вы недостаточно знаете rust, чтоб судить о его недостатках и попросту заблуждаетесь.
                          Где конкретно?

                          Строки rust не имеют ни какого отношения к строкам в java.
                          А где я говорил обратное?

                          Посмотрите на начало дискуссии: нам демонстрируют как std:swap меняет содержимое двух строк, после чего пафосно заявляют: В языке, где есть передача по ссылке, функцию swap написать можно, а в Java — нельзя.

                          На мои сначала робкие, а потом всё более настойчивые указания на то, что функция, подобная swap, вообще говоря, требует не только передачи обьектов по ссылке, но, кроме того требуется, чтобы эти обьекты были бы копируемыми (то есть изменяемыми), какомыми строки в Java не являются идёт «уход в несознанку», а потом, после дикого высера это работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен и моего примера, который показывает, что это, мягко говоря, не так — следует заявление в известном духе.

                          Если нам требуется делать что-то тёмное с неизменяемыми штуками — у нас скорее всего серьёзная ошибка в дизайне.
                          А с этим, я собственно, и не спорю. Я-то надеялся, что когда я заставлю нашего д’Артаньяна применить const_cast можно будет поговорить о гарантиях безопасности (которая в C++ и Rust обходятся, а в Java — нет), но оказалось, что проблема была намного глубже: похоже автор искренне не понимает в чём разница между передачей параметров по значению и по ссылке.

                          Если нам просто два значения поменять местами — тот, что в mem используем. Если нужно что-то сложнее — это уже априорно чорная магия, которая требует аккуратности. И в этом случае unsafe будет подсказкой, что эту часть кода надо гораздо внимательнее писать и поддерживать.
                          Ну если исходить из заявления это работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен — то нам-таки чёрная магия нужна. Что поменять местами две неизменяемые переменные-то!


                  1. khim
                    24.01.2018 13:05

                    Вы заблуждаетесь.
                    Серьёзно? Давайте пример до конца допишем, а потом уж говорить будем.

                    Пример:
                    String foo = "foo";
                    String bar = "bar";
                    swap(foo, bar);
                    // здесь foo приняло значение "bar", а bar" приняло значение "foo".
                    
                    А вы специально в примере используете не такие строки, как в Java? Давайте это исправим (реализация урезана, да, но идея, я думаю, понятна):
                    #include <string.h>
                    
                    #include <iostream>
                    
                    class String {
                     public:
                      String(const char* s) : data_(strdup(s)) {}
                      ~String() { free(const_cast<char*>(data_)); }
                      // Строки в Java immutable, имитирующие им C++ строки - тоже.
                      String operator=(String&&) = delete;
                      String operator=(const String&) = delete;
                      friend std::ostream& operator<<(std::ostream& o,
                                                      const String& s) {
                        o << s.data_;
                      }
                     private:
                      const char* data_;
                    };
                    
                    // Функию swap, пожалуйста.
                    
                    int main() {
                      String foo = {"foo"};
                      String bar = {"bar"};
                      swap(foo, bar);
                      // foo приняло значение "bar", а bar" приняло значение "foo".
                      std::cout << "foo: " << foo << "\n";
                      std::cout << "bar: " << bar << "\n";
                    }
                    

                    В языке, где есть передача по ссылке, функцию swap написать можно, а в Java — нельзя.
                    Прекрасно: допишите в мой пример свою функцию так, чтобы он компилировался и работал, потом — можно будет поговорить.


                    1. Free_ze
                      24.01.2018 13:32

                      допишите в мой пример свою функцию так, чтобы он компилировался и работал
                      Эта функция уже реализована — std::swap:

                      int main() {
                        // в Java ведь с ссылками работаем, верно?
                        auto foo = std::make_shared< String >( "foo" );
                        auto bar = std::make_shared< String >( "bar" );
                        std::swap( foo, bar );
                        // foo приняло значение "bar", а bar" приняло значение "foo".
                        std::cout << "foo: " << *foo << "\n";
                        std::cout << "bar: " << *bar << "\n";
                      }
                      ideone.com/UZ75sr


                      1. khim
                        24.01.2018 14:30

                        Контрольный вопрос: в C для вас есть «передача по ссылке» или нет?

                        Потому что раз уж вы преврашаете std::swap(String&, String&) в std::swap(std::shared&, std::shared&) и заявляете — что это, просто синтаксис такой, на самом-то деле всё так же, как в Java, то и про C'шный вариант:

                          swap(&foo, &bar);
                        
                        можно сказать, что это — у нас передача по ссылке, а что там в вызове знаки & стоят — ну так это потому, что в C так принято…


                        1. Free_ze
                          24.01.2018 15:05

                          Потому что раз уж вы преврашаете std::swap(String&, String&) в std::swap(std::shared&, std::shared&) и заявляете — что это, просто синтаксис такой, на самом-то деле всё так же, как в Java
                          Вы пытаетесь доказать свою точку зрения с помощью семантически разного кода. Я «превратил» этот код в адекватный Java-семантике.

                          можно сказать, что это — у нас передача по ссылке, а что там в вызове знаки & стоят — ну так это потому, что в C так принято…
                          Можно. Тогда соглашусь, что условно «передачей по ссылке» в Java можно считать костылики с одноэлементными массивами и прочими мутабельными обертками (= Возможно еще что-то из sun.misc.Unsafe (если еще не выпилили). Остальное все — передача по значению, ибо никакой другой возможности ссылаться на ссылки в Java нет.


                          1. khim
                            24.01.2018 17:39

                            Вы пытаетесь доказать свою точку зрения с помощью семантически разного кода. Я «превратил» этот код в адекватный Java-семантике.
                            Проблема в том, что «передача по ссылке» vs «передача по значению» — это не о семантике. Это о синтаксисе.

                            В частности, считается, что передачи по ссылке в C нет, а в C++ — есть. Но ведь легко убедиться, что C'шная void swap(int*, int*); и C++'ная void std(int&, int&); не просто «близкие родственники»! На уровне машинного кода они просто идентичны! Более того, при использовании ICF — это вообще будет одна и та же функция!

                            Остальное все — передача по значению, ибо никакой другой возможности ссылаться на ссылки в Java нет.
                            Что это за передача по значению такая, при которой обьект (если он, конечно, mutable) может быть испорчен? Хочу вам напомнить чем вообще эти два варианта отличаются: ри вызове по значению, выражение-аргумент вычисляется, и полученное значение связывается[en] с соответствующим формальным параметром функции (обычно посредством копирования этого значения в новую область памяти). При этом, если язык разрешает функциям присваивать значения своим параметрам, то изменения будут касаться лишь этих локальных копий, но видимые в месте вызова функции значения останутся неизменными по возвращении

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

                            Можно, конечно, «расщепить» типы данных и ссылки на эти типы данных и говорить о том, что ссылки передаются по значению… но учебники Java так не делают и, как уже говорилось выше, передача по значению отличается от передачи по ссылке в первую очередь синтаксически, а вот семантической разницы может и не быть (см. указатели в C и ссылки в C++).


                            1. Free_ze
                              24.01.2018 17:55

                              Проблема в том, что «передача по ссылке» vs «передача по значению» — это не о семантике. Это о синтаксисе.
                              Разница между объектом в стеке и указателем на объект в хипе очень даже семантическая.

                              Что это за передача по значению такая, при которой обьект (если он, конечно, mutable) может быть испорчен?
                              По значению вы передаете ссылку, которая копируется. Поэтому ссылку повредить/переназначить вы не сможете никак. В родственном C# для этого есть ref/out-модификаторы, что подчеркивают эту разницу. В Java же распространенной практикой (по крайней мере Хорстман с Кеем об этом пишут) является обёртывание объекта в массив единичной длины.

                              Можно, конечно, «расщепить» типы данных и ссылки на эти типы данных и говорить о том, что ссылки передаются по значению… но учебники Java так не делают
                              Потому что это учебники про язык, а не про платформу.


                              1. myrslok
                                24.01.2018 18:04

                                Потому что это учебники про язык, а не про платформу.

                                На самом деле учебники, несомненно, "так делают", т.е. различают объекты и ссылки (включая учебники начального уровня, например, Head First).


                                Более того, это в явном виде написано по ссылке выше ("… в сообществах Java и Visual Basic ту же семантику часто описывают как «вызов по значению, где „значением“ является ссылка на объект»...").


                              1. khim
                                24.01.2018 18:21

                                Разница между объектом в стеке и указателем на объект в хипе очень даже семантическая.
                                Да, но вопрос передачи аргументов по ссылке или значению — он немного ортогонален, хотя в Java они связаны: все обьекты, размещённые в стеке передаются по значению, все обьекты, размещённые в куче — по ссылке.

                                В C++ — это не так, можно передавать по значению как обьекты размещённые в стеке, так и обьекты, размещённые в куче — и по ссылке тоже можно передавать и те и другие.

                                Но и в Java и в C++ для функции, подобной swap мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!


                                1. Free_ze
                                  24.01.2018 18:44

                                  в Java и в C++ для функции, подобной swap мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!
                                  Если мы хотим изменить состояние объекта, то нельзя. Но если мы хотим поменять местами объекты (в чем и кроется смысл swap), то в C++ это делается запросто чистыми синтаксическими средствами, в Java — только неочевидными хаками с целью преодолеть и превозмочь.


                                  1. khim
                                    24.01.2018 19:22

                                    Но если мы хотим поменять местами объекты (в чем и кроется смысл swap)
                                    Нет, нет и нет.

                                    Вот тут уже даже открыли почти все буквы, но не смогли прочитать слово. std::swap(int&, int&), скажем, делает следующее:

                                    1. Создаёт временный обьект типа int.
                                    2. Копирует значение первого аргумента в этот временный обьект.
                                    3. Копирует значение второго аргумента в первый.
                                    4. Копирует значение временного обьекта во второй аргумент.
                                    в C++ это делается запросто чистыми синтаксическими средствами, в Java — только неочевидными хаками с целью преодолеть и превозмочь
                                    Это как? Это хде? Единственное отличие C++ от Java — в том, что для всяких обьектов типа std::string определён operator=, который замещает содержимое обьекта содержимым другого, родственного по типу, обьекта.

                                    Заведите интерфейс Assignable в Java с функцией Assign — и будет вам swap для. Вот прям такой же, как в C++. В точности.

                                    Какое вообще имеет отношение наличие метода operator= в классе std::basic_string и отсутствие аналогичного метода в неизменяемом классе String к способу передачи параметров???

                                    На самом деле сакральный смысл существования функции std::swap не в том, чтобы избежать копирования, скажем, int'ов (это и не нужно и невозможно). Нет — фишка в другом: специализации std::swap (скажем std::swap(std::basic_string)) могут не создавать временный обьект и не копировать всё содержимое (в частности std::swap(std::basic_string) вызывает std::basic_string::swap, а та уже, являясь функцией обьекта, может добраться до его содержимого и «дёшево» переставить указатели, вместо того, чтобы «дорого» копировать строки). Опять-таки вся эта деятельность может быть в точности возспроизведена в Java и, если бы карты легли иначе и, кроме интерфейса Cloneable Java имела бы и похожий интерфейс Assignable, то все эти чудеса можно было бы воспроизвести точь-в-точь в Java. Хотите — сделайте свою библиотеку с классами CppStyleString и функций Swap, определённой для неё. В языке для этого ничего менять не потребуется.


                                    1. Free_ze
                                      25.01.2018 12:28

                                      Это понятно, мутаторную форму std::swap для std::basic_string мы условились не использовать.

                                      Но сути не меняет: в Java строки — это объект ссылочный, полностю лежащий в хипе. Поэтому хорошего аналога String foo = {"foo"}; там не сделать. Ну вообще никак. (Более того, сами строки размером до sizeof(_CharT*) в типичных имплементациях аллоцируются на стеке) Поэтому просто ссылки не катят. Нам нужны указатели и полностью хиповый объект! С чего мы и начали этот длинный тред.


                                      1. khim
                                        25.01.2018 13:12

                                        Это понятно, мутаторную форму std::swap для std::basic_string мы условились не использовать.
                                        Тем не менее с неё и начался весь этот субтред.

                                        Поэтому просто ссылки не катят. Нам нужны указатели и полностью хиповый объект! С чего мы и начали этот длинный тред.
                                        Нет. Нужны ссылки. Союственно «иностранное» название call-by-reference как бы намекает.

                                        Полным аналогом версии из Java в C++ будет следующее:
                                        const std::string& foo = std::string("foo");
                                        const std::string& bar = std::string("bar");
                                        std::swap(foo, bar);
                                        

                                        И, сюрприз, сюрприз, сюрприз: это — тоже не работает. Даже не компилируется.


                                        1. Free_ze
                                          25.01.2018 13:28

                                          Тем не менее с неё и начался весь этот субтред.
                                          Краткий экскурс по треду, для тех, кто только пришел: я поправил человека и даже как-то косвенно говорил о том, что он был не прав. Далее я привел код, с которым мой оппонент так же не был согласен.

                                          Полным аналогом версии из Java в C++ будет следующее
                                          У вас мутабельный объект в стеке. Попробуйте еще раз.


                                        1. Free_ze
                                          25.01.2018 14:00

                                          Нет. Нужны ссылки. Союственно «иностранное» название call-by-reference как бы намекает.
                                          Там call-by-reference и происходит) Указатели передаются по ссылке. Только в Java «безопасные указатели» называются ссылками.


                                1. poxvuibr
                                  25.01.2018 10:21

                                  Да, но вопрос передачи аргументов по ссылке или значению — он немного ортогонален, хотя в Java они связаны: все обьекты, размещённые в стеке передаются по значению, все обьекты, размещённые в куче — по ссылке.

                                  В Java все переменные передаются по значению. Только значение переменных, указывающих на объект это, как несложно догадаться, указатель :)


                                  1. khim
                                    25.01.2018 13:32
                                    +1

                                    В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на C++ тоже принимает не обьекты, а ссылки на объекты (вот тут даже её код в три строки приведён) и она эти ссылки не меняет (собственно в C++ и нет вообще никаких методов, позволяющих изменять ссылки).

                                    Что гораздо хуже — в языках типа Algol'а и Pascal'я, откуда пошли эти термины («вызов по ссылке» и «вызов по значению») в функцию, которая описывает параметр не как x : Integer, а как var x : Integer тоже передаётся не обьект, а ссылка.

                                    Рассмотрите следущий код для языка Pascal (учили в школе, аль нет?):

                                    procedure foo(var a : String, var b : string);
                                    begin
                                      swap(a, b);
                                    end;
                                    

                                    Рассмотрите этот пример. Это — самое эталонное, самое «true», «лошадь ростом в метр и весом в один килограмм» pass-by-reference. То, с чего пошла вся терминология. До того, как в языка программирования появилась куча и даже стек (да-да — в ранних языках программирования стека не было).

                                    И что же мы тут видим? Правильно: мы видим две ссылки на объекты типа String, которые при вызове функции swap копируются — после чего функция swap их изменять не может, но может изменять то, на что они указывают!

                                    Почему, блин, вещь, которая полвека назад называлась pass-by-reference, транслируясь почти в такой же байткод как в Java (да-да, вы не поверите, но Pascal тоже транслировался в байткод, как и предшественник C, BCPL) стала вдруг называться «pass-by-value». С какого, я извиняюсь, перепугу?


                                    1. poxvuibr
                                      25.01.2018 14:25

                                      В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на C++ тоже принимает не обьекты, а ссылки на объекты (вот тут даже её код в три строки приведён) и она эти ссылки не меняет (собственно в C++ и нет вообще никаких методов, позволяющих изменять ссылки).

                                      Вот вам код на C++, демонстрирующий, что есть в Java, а чего нет


                                      Код под спойлером
                                      #include <iostream>
                                      
                                      using namespace std;
                                      
                                      class TestStruct {
                                          public:
                                          int id;
                                          int count;
                                      
                                          TestStruct(int id, int count) {
                                              this->id = id;
                                              this->count = count;
                                          }
                                      
                                          TestStruct(const TestStruct& obj) {
                                              this->id = obj.id;
                                              this->count = obj.count;
                                              cout <<"copy constructor working " << std::endl;
                                          } 
                                      };
                                      
                                      int main()
                                      {
                                          {
                                              TestStruct s1(1,1);
                                              TestStruct s2(2,2);
                                      
                                              cout <<"copy constructor will be invoked, java can do something like that" << std::endl;
                                              std::swap(s1, s2);
                                      
                                               //Java can do something like that
                                              cout <<"s1.id should be 2 and is " << s1.id << std::endl;
                                          }
                                      
                                          {
                                          TestStruct* s1 = new TestStruct(1,1);
                                          TestStruct* s2 = new TestStruct(2,2);
                                      
                                          cout <<"References will be copied, java can't do that" << std::endl;
                                           //Java can't do that
                                          std::swap(s1, s2);
                                      
                                          cout <<"s1->id  should be 2 and is " << s1->id << std::endl;
                                          }
                                      
                                          return 0;
                                      }


                    1. myrslok
                      24.01.2018 13:39

                      А вы специально в примере используете не такие строки, как в Java?

                      Не понял, что вы имеете в виду. Приведенный (невозможный) код написан на Java.


                      Дописывать и вообще разбирать ваш код мне неудобно и недосуг, извините. Вот (найденный) код с использованием std::swap:


                      #include <iostream>
                      
                      using namespace std;
                      int main()
                      {
                          int a, b;
                          cin >> a;
                          cin >> b;
                          swap(a, b);
                          cout << a;
                          cout << b;
                          return(0);
                      }

                      В Java так сделать нельзя, что с int, что с Integer.


                      Повторюсь, если вы думаете, что в Java агрументы передаются по ссылке, вы заблуждаетесь, причем заблуждаетесь капитально. Рекомендую погуглить что-то вроде "is Java pass-by-value". Например, на Stackoverflow это подробно разобрано. Ну или вот (часть 8.4.1 спецификации Java 8):


                      When the method or constructor is invoked (§15.12), the values of the actual argument expressions initialize newly created parameter variables...


                      1. khim
                        24.01.2018 14:21

                        Я боюсь это становится вопросом терминологии. Можно либо считать, что в Java всё и всегда передаётся по ссылке, но взять «ссылку на ссылку» нельзя, потому что обьекты, размещённые на стеке, недоступны GC, либо разделять обьекты (хранящиеся в куче) и ссылки на них (размещённые на стеке).

                        В первом случае — параметры у нас таки передаются по ссылке, но агрументом является обьект String (как, собственно, в коде и написано), либо, альтернативно, что есть ещё и ссылки где-то (которых в коде, в общем-то, не видно, они для нас как тот «суслик») — тогда передача происходит по значению, но всё окончательно запутывается.


                        1. myrslok
                          24.01.2018 14:32
                          -1

                          Судя по тому, что вы написали выше ("Передача по ссылке в C++ даёт вам те же возможности, что и обычная передача для больгинства типов в Java."), у вас была содержательная, а не терминологическая ошибка в картине мира.


                          А что до терминологии, то она вполне стандартна, и "считать, что в Java всё и всегда передаётся по ссылке" нельзя.


                          1. khim
                            24.01.2018 17:17

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


                      1. khim
                        24.01.2018 14:27

                        В Java так сделать нельзя, что с int, что с Integer.
                        В Java так сделать нельзя, не потому, что Integer передаётся по значению, а потому что он Immutable. Mutable вариант — это, например int[] с одним элементом.

                        Не понял, что вы имеете в виду.
                        Во всех ваших примерах на Java вы используете immutable типы. И изменить их нельзя не потому, что они «переданы по значению», а потому что они, в принципе, неизменяемы.

                        В моём примере String — тоже неизменяемый и, внезапно, его «так просто» изменить нельзя — даже при передаче по ссылке.


                        1. myrslok
                          24.01.2018 14:44

                          Изменяемость типа с этими вопросами не связана, см. другой мой коммент. К инстансу ArrayList указанные аргументы применимы в равной степени.


                          std::swap именно меняет ссылки, а не изменяет каждое из значений. В приведенном мной коде, кстати, int. И это работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен.


                          1. khim
                            24.01.2018 17:26

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

                            Ещё раз, для идиотов. Для работоспособности функций, подобных std::swap нужно выполнение не одного, двух условий:

                            1. Передача аргументов в функцию должна происходить не по значению, а по ссылке.
                            2. Переданные значения не должны быть immutable (для них должен быть определён operator=).

                            Только при выполнении обоих условий функция, подобная std::swap реализуема. Для примитивных типов в Java не выполняется первое условие, для типов Integer и String — второе, но возьмите ArrayList — и всё получится!


                            1. myrslok
                              24.01.2018 17:36

                              Ещё раз, для идиотов.

                              Уверен, что вы кругом неправы в основах, но разговор окончен.


    1. ad1Dima
      22.01.2018 06:04

      Ну хоть бы взять пример выше — LINQ позволяет сделать код куда компактнее, чище, проще для понимания, но ухудшает эффективность.

      В чем LINQ ухудшает эффективность? Когда он плохо в sql смаплен?


      1. leotsarev
        22.01.2018 07:50

        Если говорить про Linq-to-Objects — многовато лишних аллокаций.


        1. ad1Dima
          22.01.2018 07:55

          Кстати, тут на днях появилось (сам еще толком не смотрел) github.com/kevin-montrose/LinqAF


        1. andreycha
          23.01.2018 17:41

          Только большинству приложений это по барабану.


  1. Zelgadiss
    21.01.2018 22:57

    Спорное мнение. В итоге производительность программы теряется в угоду усиленной читабельности кода.


    1. lair
      21.01.2018 23:41

      Во-первых, не всегда. Во-вторых — ну да, иногда теряется. Это далеко не всегда настолько важно.


    1. bormotov
      21.01.2018 23:57

      производительность процессоров растет быстрее, чем производительность людей.
      В тех 7% где не растет — будет не тупой код, и комментарий «не трогайте тут, выигрываем несколько тактов»


      1. Splo1ter
        22.01.2018 15:31

        Ну, как совет при оформлении PR в ASP.NET Core, пишут в гайдлайнах избегать Linq в hot-path частях.


        1. andreycha
          23.01.2018 17:44

          Потому что там это имеет смысл. Но подавляющему большинству приложений далеко до ASP.NET Core в плане «горячности» hot path'ов. А каким-то это и вовсе неважно.


      1. september669
        22.01.2018 16:48

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


        1. Mendel
          22.01.2018 17:00
          +3

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


          1. Free_ze
            22.01.2018 17:04

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


            1. VolCh
              22.01.2018 17:37

              Абстракции увеличивают тормоза частенько :)


              1. Free_ze
                22.01.2018 17:47

                Вообще любой код увеличивает тормоза) Но есть же умный компилятор и здравый смысл.


        1. bormotov
          22.01.2018 17:01

          не понимаю в чем проблема.

          Если при написании программы, у вас спрашивали требования (нужно что бы быстро, нужно что бы несколько штук одновременно), то значит, нужно идти к авторам и тыкать пальцем «вот тут не соответствует».
          Если же это массовый продукт, то видимо есть некий механизм, для обратной связи, и можно верить, что этот механизм рабочий.

          Чудес, к сожалению, в этом месте не бывает. Вот, у меня в списке дежурных фраз есть

          Тупые решения выигрывают у умных в 9 из 10 случаев. Будьте достаточно умны, что бы предлагать тупое решение

          Практически вся эта статья в одной фразе.


      1. boblenin
        22.01.2018 18:44

        Ну вот я обновил 3770k на 8700k — по бенчмаркам разница 2x. Это 6 лет прошло. С одной стороны за 6 лет новичок вполне обучается в опытного разработчика и его производительность более чем удваивается. С другой стороны дополнительных абстракций в софт за 6 лет добавлено вполне достаточно, чтобы двукратный рост производительности процессоров нейтрализовать. И это если не считать ущерб от meltdown.


      1. Alex_ME
        23.01.2018 02:36

        Так-то да, а потом ты запускаешь Atom на ноутбуке старше 3х лет и начинаются боль и страдания.


    1. africaunite
      22.01.2018 01:01

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


      До середины 90-х нам иногда приходилось оптимизировать и коэффициент k в алгоритмах с линейной сложностью O(kN), но и тогда, когда компьютеры были бледнее "Малины", кроме того, что такие приемы были редки и очевидны — очень важно было следить за "внятностью речи".


      1. AllexIn
        22.01.2018 12:38

        Архитектура — это основа читабельности. И именно в ней чаще всего приходится жертвовать читабельность.


        1. boblenin
          22.01.2018 18:45

          Архитектура приложения — это первое, что не выдерживает столкновения с реальностью.


    1. Ohar
      22.01.2018 02:27

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


      1. AllexIn
        22.01.2018 12:39

        Круто, если в приложении производительность зависит от алгоритма, который легко отделяется в черный ящик. Хуже, когда этот алгоритм через весть проект идет.


        1. Mendel
          22.01.2018 14:13

          Неее. Нормально когда ЛЮБАЯ часть приложения превращается в черный ящик и все приложение состоит из черных ящиков (которые впрочем на самом деле белые).
          И кошмар когда этого нельзя сделать.
          Вроде смысл не сильно меняется, но акценты именно так стоят ибо как правило невозможность разнести в черные ящики это проблема архитектуры.


          1. boblenin
            22.01.2018 18:46

            Т.е. кошмар — это всегда.


    1. poxvuibr
      22.01.2018 15:26

      Тупой код обычно состоит из каких-то стандартных паттернов и стандартных приёмов. Таких приёмов, которые умеют находить и распознавать и оптимизировать компиляторы, интерпретаторы и JIT.


  1. OlegBrony
    21.01.2018 22:57

    но совершенствоваться тоже ведь нужно… не даром придуманы всякие конструкции?


    1. lair
      21.01.2018 23:41

      "Совершенствование" состоит как раз в том, чтобы понимать, когда "всякие конструкции" надо применять, а когда — нет.


  1. your_eyes_lie
    21.01.2018 22:57
    +1

    1. Mendel
      22.01.2018 13:48

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


    1. Chaos_Optima
      22.01.2018 16:09

      Вся статья собственно говоря как раз про KISS


  1. KirEv
    21.01.2018 23:34

    в прошлом месяце поймал себя на мысли: сделать просто — сложно.

    этот крутой чувак, из мира Java, кажется говорил не про метод использование if, switch, etc., а про метод реализации конкретных алгоритмом, реализация которых в чистом коде дает понять при чтении: это функция f, они принимает аргумент x и возвращает y, и в тупом коде ясно как день без лишних комментариев каким образом c x сделался y.

    … другой вопрос — именование, не менее сложная задача чем писать простой код…

    да и вообще, одна из любимых фраз: простые вещи ломаются реже сложных.


    1. VolCh
      22.01.2018 09:52

      Искусство хорошего именования — часть искусства написания хорошего кода.


      1. danfe
        22.01.2018 14:19

        Очень важная часть! Naming things and cache coherency are the hardest problems in CS. :-)


  1. lxsmkv
    22.01.2018 00:37

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


    1. Acuna
      22.01.2018 08:21

      код, который может понять даже человек не знакомый с этим конкретным языком программирования
      Не к этому ли нужно стремиться?


      1. sand14
        22.01.2018 12:49

        код, который может понять даже человек не знакомый с этим конкретным языком программирования
        Не к этому ли нужно стремиться?

        Вряд ли к этому.


        Допустим, мы пишем чистый и понятный код.
        При этом к месту используем лямбды, LINQ (C#) или Stream API (Java).
        Но ведь до сих пор много разработчиков не освоили функциональный подход, хотя он есть в C# аж лет 10, и почти 4 года в Java.
        Что же, нам не использовать его, чтобы все могли прочитать?


        1. Neikist
          22.01.2018 13:17

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

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


          1. Mendel
            22.01.2018 13:53

            Давайте оставим солонку уязвимой а общипанную курицу — человеком. Ну и так же ясно о чем речь). А то сейчас добавим «и при знании основ синтаксиса»… потом отрефакторим, добавим, уберем, опять добавим, опять перепишем и покроем тестами…


            1. lxsmkv
              23.01.2018 02:40

              Хотя я должен заметить, что малость загнул. Сказать сказал, а вот как это в жизни выполнить… правильно, можно только стремиться к этому. Я и сам бывает ляпну где нибудь так, а где-то эдак. Так что, не мне камни кидать, так сказать. Ведь и мне кажется в тот момент, что так сейчас удобно, понятно, и очевидно. А бывает просто ради эксперимента какую-то конструкцию напишешь. А вот удивлюсь ли я этому коду через три месяца — не знаю.
              Сегодня тупил над кодом в продукте (на яве, переменные все числовые, названия изменены):

              return x1 > x2 && y1 == y2; 
              сам пишу в основном на питоне (GUI-тесты) и в нем можно соединять логическими операторами числа и булевы значения. А в яве это невозможно, и поэтому скобки посчитали ненужными. Я спросил, а что сложного поставить скобки, да и вообще дать обозначение каждому условию и вынести их в переменные, так и логгирование будет проще делать, если придется. Мне сказали, ну, да можно делать, можно не делать. Придумывать название переменным мол то еще мучение.
              А потом я же слушаю от этих ребят какой у них нечитаемый код, и что там черт ногу сломит. Год назад они говорили как будет здорово, если старый код можно будет выкинуть и написать все заново. Вот у них появилась возможность. И что мы видим через год — те же яйца только в профиль. Даже не знаю, что сказать.


          1. sand14
            22.01.2018 22:25

            А вот всякие нагромождения тернарных операторов в тернарных операторах, или десятки параметров метода на километр

            Опять же, из практики — такое как раз встречается в коде, который состоит из последовательных процедурных портянок, позиционирующихся как простой и понятный код.


        1. Acuna
          23.01.2018 02:03

          То, что Вы описали — базовые навыки, которые должны быть знакомы каждому джуниору, это как в веб-разработке постигать азы верстки. Поэтому если джуниор еще не в курсе об этих технологиях и пишет код без них (что, к слову, наоборот усложняет код в случае без тех же лямбд), то это остается исключительно на совести этого джуниора. Однако лично для меня сложный код тот, который подчас неоправданно усложнен, и не в плане технологий, а именно в плане того, что многие вещи можно было бы написать намного проще, что, как следствие, привело бы к лучшей читаемости программерами всех уровней, эдакая кроссплатформенность в плане опыта, а кроссплатформенность — это всегда хорошо) Большей частью я и занимаюсь рефакторингом лапшекода от таких вот писателей, и у меня возникает чувство, что код пишется как-то сам собой, а они в этот момент находятся в каком-то другом месте, в любом случае, о простоте и читаемости они явно не думают, однако писать просто — это сложно, я знаю по себе. Часто я даже хожу по офису и обдумываю как я могу написать что-либо проще. А потом еще проще. Можно наговнокодить, а можно потратить на тридцать минут времени больше и родить код, действительно понятный даже ребенку. Но это нужно любить работу и проект, возможно в этом и дело. Ну и плюс иметь возможность вот так ходить и размышлять о своем. Поэтому чаще всего на выходе получается «хоп-хоп и в продакшн» с кучей костылей. Но работает ведь, так что лучше не трогать. А то, что там все упадет когда кто-то оставил коммент — дак поэтому же лучше и не трогать)


          1. sand14
            23.01.2018 10:22

            То, что Вы описали — базовые навыки, которые должны быть знакомы каждому джуниору, это как в веб-разработке постигать азы верстки. Поэтому если джуниор еще не в курсе об этих технологиях и пишет код без них (что, к слову, наоборот усложняет код в случае без тех же лямбд), то это остается исключительно на совести этого джуниора.

            Ох, да в моей практике как раз сеньоры не хотят "учить" лямбды и ФП.


            1. Acuna
              23.01.2018 21:26

              Вообще, если говорить уж совсем на чистоту, лямбды действительно делают код короче, но никак не проще. В прошлом комменте под усложнением я подразумевал именно увеличение его длины. Для меня код является сложным, когда на его чтение затрачивается больше времени, чем могло бы. Лямбды в данном случае делают код короче, и его чтение осуществляется быстрее, однако сами по себе они имеют в себе магию, которая, очевидно, явно не способствует его очевидности и простоте. Поэтому я все-таки я уже начал смотреть выше джуниора, однако если мы говорим все-таки о простом и понятном коде, тут все-таки стоит смотреть на него глазами джуниоров, благодаря чему мы и получим простой код, но все же уже без «лябд и ФП».


              1. sand14
                23.01.2018 23:29
                +2

                Чтобы понять лямбды, достаточно знать, что лямбда это не код, который выполняется непосредственно в месте написания, а правило — математическая формула, которая будет использована как паттерн стратегия.


                И в этом смысле не так уж сильно лямбда отличается от простой процедуры.
                Процедура это блок кода, который тоже выполняется не в месте его написания, а где-то в другом месте при его вызове.


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


                Также неясно — уже столько лет носятся с паттернами (на одном хабре уж лет 10 как, а если копнуть глубже, то и сильно побольше), а самый понятный и полезный паттерн стратегия, позволяющий реализовывать декомпозицию и инверсию контроля, так и не осилили (хотя взять тот же DI — тоже стратегия в чистом виде, только прокидываемая не в виде лямбд, а в виде интерфейсов).


                Получается, давно пишем на мультипарадигменных языках, включающие ОО и ФП, а на деле копни — вся индустрия (за всех, конечно, говорить, не будем, но тем не менее) пишет и защищает процедурные портянки.


                1. ZyXI
                  24.01.2018 01:04

                  Меня бы объяснение, что лямбда — это оказывается реализация паттерна стратегия только больше бы запутало. Лямбды — это ссылки на функции, функции определяются прямо на месте. Если вам всё ещё не понятно, представьте код лямбды, который вынесли в отдельный метод, а его вызов вставили в место вызова лямбды, но не в место её определения.


                  А зачем носятся с паттернами на хабре я не знаю, я как?то прочитал их описание, понял, что мой мозг может сгенерировать что?то подобное и без знания, что это какой?то паттерн, и благополучно забыл бо?льшую часть прочитанного про паттерны. Учитывая, как «часто» мне потом приходилось слышать про паттерны от иностранных коллег во время собственно работы над OS проектами (Python сначала, C и lua сейчас), а также от русских коллег во время работы на собственно работе (LabVIEW, C и ассемблер), могу заключить, что паттерны мне нужны только на уровне «тот парень сказал „visitor“ и я понял, что он сказал». Конечно, ассемблер не особо располагает к применению шаблонов, но про остальные я такого не скажу.


                  1. DistortNeo
                    24.01.2018 01:38

                    Я бы тоже ничего из такого объяснения не понял.

                    Почему лямбда — не код, когда это код? Лямбда — это синтаксический сахар ЯП для классов-функторов. А весь этот матан про лямбды хорошо понятен только чистым математикам-теоретикам, программистам-практикам он только мешает.

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

                    Ну и аналогия с паттерном «стратегия»: я ни разу не видел, чтобы этот паттерн оформляли именно таким образом (т.е. с именами, содержащими Strategy). Поэтому такое объяснение тоже было бы непонятно.


                    1. VolCh
                      24.01.2018 09:10

                      я ни разу не видел, чтобы этот паттерн оформляли именно таким образом (т.е. с именами, содержащими Strategy)

                      Всё же встречается. Как правило в случаях когда паттерн используется сознательно. Впрочем, это и к другим паттернам относится.


                    1. sand14
                      24.01.2018 10:44
                      +1

                      Почему лямбда — не код, когда это код? Лямбда — это синтаксический сахар ЯП для классов-функторов.

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


                  1. lair
                    24.01.2018 09:36

                    Лямбды — это ссылки на функции, функции определяются прямо на месте.

                    В C#, скажем, это не совсем так (приблизительно начиная с того места, где деревья выражений).


                  1. sand14
                    24.01.2018 11:31

                    А зачем носятся с паттернами на хабре я не знаю, я как?то прочитал их описание, понял, что мой мозг может сгенерировать что?то подобное и без знания

                    К слову, если смотреть периодическую литературу, то носиться с паттернами, включая MVC, MVVM и иже с ними (визиторы, обсерверы и прочая), начали лет 20 назад (тогда же началась пропаганда аджайла и скрама).
                    А так то, все началось еще раньше — вспомнить историю того же SOLID.


                    И что всегда удивляло — если действовать по здравому смыслу, то так и так получаются SOLID и все прочие паттерны.
                    Это под силу вывести даже одному человеку.


                    И я не против того, чтобы подобные подходы были формализованы в виде унифицированных доступных всем описаний. Только за.
                    Только пока получается так, что разговоров больше, а применения паттернов на практике — меньше.
                    Смотришь какой-нибудь MVVM-проект — да, формально MVVM есть, т.к. фреймворк обязывает, да еще DI прикручен, а в коде почему то все равно — сильная связность и лапша.


                  1. Mendel
                    24.01.2018 13:17

                    Если вам всё ещё не понятно, представьте код лямбды, который вынесли в отдельный метод, а его вызов вставили в место вызова лямбды, но не в место её определения.

                    А почему тогда нельзя было вот прям так и сделать?
                    Вот просто взять и вынести в отдельный метод. почему нет?
                    Я спрашиваю не потому что «тому кто не понимает в лямбдах будет проще читать». Просто хорошим тоном является выделять каждую логическую единицу в отдельный метод. При этом большинство кодгайдов дают рекомендации «метод должен быть не больше чем хх LOC». А лямбда которая могла бы быть отдельным методом — у нас его тупо расширяет.
                    Я вижу только две причины:
                    1) лямбда ну очень короткая
                    2) сложно придумать этому методу говорящее название
                    Однако пункт два является явным поводом задуматься, а не с запашком ли у нас код…


                    1. alexeykuzmin0
                      24.01.2018 13:35

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


                      1. Mendel
                        24.01.2018 14:33

                        При условии что она маленькая.
                        Меня тут позвали в один проект который я одиннадцать лет запускал, потом продал, потом дорабатывал по контракту, потом дикие люди к нему кучу всего понаписывали и вот вдруг надо «чуть-чуть доделать» ну и «просто посмотреть насколько оно нормально».
                        Я на это посмотрел и сказал что пока там не приберутся я даже смотреть не буду (ага, вот так я и смогу не смотреть). Теперь две недели войны на тему «ну почему же мне надо разделять метод экшена в контроллере, ну и что что он на 270 LOC, ну и что что это ТТУК и вообще пять вложений ифов это перебор, но ведь это же часть реализации, оно будет лучше читаться если будет внутри, и скрыто от всех. Я плюнул на то чтобы что-то объяснять, просто накатал план по рефакторингу с жесткими метриками где не надо думать а тупо смотреть — IDE сказала что в этом методе больше 20 строк — дели. Зачем — не твоя забота. Как — посоветую.
                        Только после того как поверх этого появилась „виза“ закачика о том, что пока не будет сделано з/п не видать — все начало двигаться.

                        К чему я это? Мы конечно можем сказать что лямбды и тернарники улучшают читаемость в тех местах где они уместны, но… тот кто понимает разницу — в таком совете не нуждается, а тому что не понимает — нужны более детерминированные метрики.
                        С тернарниками я для себя решил „только если результат константа, или один вызов одного метода и колво параметров у методов не больше двух“.
                        С лямбдами — »если общий размер метода будет укладываться в лимит, и я понимаю как я бы я назвал метод если бы он не укладывался и я бы вынес".


                        1. alexeykuzmin0
                          24.01.2018 14:36

                          При условии что она маленькая.
                          Да, при условии, что она маленькая. Потерял эту фразу при рефакторинге комментария.
                          Разумеется, в первую очередь нужно думать головой и смотреть, получается ли код читаемым (или, возможно, в этом конкретном месте что-то другое важнее читаемости), и отталкиваться уже от этого.


                          1. VolCh
                            24.01.2018 14:38
                            +1

                            Тогда это не рефакторинг :)


                            1. alexeykuzmin0
                              24.01.2018 14:42

                              Не согласен. Это рефакторинг, потому что задумывался как изменение формы на более читаемую без изменения смысла. Но с багой, потому что смысл все-таки изменил.


                    1. Chaos_Optima
                      24.01.2018 19:43
                      +1

                      Вот просто взять и вынести в отдельный метод. почему нет?

                      • Короткая
                      • Используется 1 раз
                      • Замыкание


    1. Acuna
      23.01.2018 05:42

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


  1. leventov
    22.01.2018 01:08

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


    1. Mendel
      22.01.2018 14:01

      Ну я бы сказал что статья чуть утрированна и речь скорее идет о юниоре и мидле.
      Чтобы быть юниором нужно знать язык/платформу, быть знакомым с инструментами и уметь написать хелловорд.
      Чтобы быть мидлом необходимо владеть основными принципами «простого» кода на уровне «могу выполнять», уметь соблюдать код-гайды и иметь уверенные навыки работы с инструментами. Для этого уже нужно немного опыта (в идеале с наставником, так быстрее).
      Сеньору же да, нужно много опыта чтобы не просто уметь использовать парадигмы и инструменты но и четко понимать почему нужно именно так а не иначе (мидлу хватит и «потому что БОСС так сказал»).


    1. punkkk
      22.01.2018 14:32

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

      Ну еще ЗП у них разные, если уж на то пошло. :D


      1. Mendel
        22.01.2018 14:51

        У сеньора не бывает нежданчика. Сеньор ЗНАЕТ что жопа рано или поздно случится, и все время к ней готовится. Поэтому и знает что делать).


    1. africaunite
      22.01.2018 15:01

      В большинстве видов деятельности человека — уровень "сеньорности" определяется, в первую очередь, стабильностью определенного качества результата.


  1. Rambalac
    22.01.2018 11:20
    -1

    Сеньор-помидор там или нет. Но реально замечаю, что за новичками, или точнее сказать индусами или вьетнамцами, основной способ улучшения или даже исправление их кода, это в основном удаление по Ctrl+l.
    Другая их большая проблема, это совершенное непонимание, что будет с их кодом написанном в одном таске буквально на следующий день в их же следующем таске. Простейшее решение для них, это копипаста и последующее исправление всех (или нет) копипаст в случае изменений.


    1. fatronix
      22.01.2018 23:09
      -1

      Не знаю, за что человека заминусовали, но в работе с индусами (не образными) действительно всё так.


  1. LionAlex
    22.01.2018 11:49

    Simplicity is complicated

    © Rob Pike


  1. SirEdvin
    22.01.2018 12:40

    А потом это все выходит в «нам на приложение, которое читает rss нужно 5 серверов».

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


  1. arturpanteleev
    22.01.2018 12:50

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


    1. Free_ze
      22.01.2018 13:48

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


  1. sand14
    22.01.2018 12:55

    В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом (кстати, тогда не стоит называть его тупым):


    Неслучайно появился первый комментарий — конечно, код в нем утрирован (хотя… и подобное доводилось видеть в рабочих проектах).


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


    1. Free_ze
      22.01.2018 13:51

      В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом

      Это и в книгах порой не раскрывается, что уж говорить про статью.

      image


      1. khim
        23.01.2018 04:08

        Эта картика — весьма популярна, но ведь самый простой способ уменьшить эту метрику — это увеличить количество минут. Правратив, скажем, 100 строк «хитрого» кода с 10 WTF/минуту в 10'000 строк «хорошего» кода с 1 WTF/минуту… но точно ли это улучшиение, если общее количество WTF'ов после этого возрастает?


        1. ad1Dima
          23.01.2018 06:13

          нет однозначного ответа на этот вопрос. Иногда 10 000 строк кода с меньшей плотностью WTF бывает лучше, чем 100 строк года делающих то же самое через один сплошной WTF.

          Можете просто добавить ещё одну метрику WTF/стока кода и смотреть сразу по двум.


        1. Free_ze
          23.01.2018 09:40

          Вряд ли количество кода для отдельного случая будет отличаться на два порядка) В 2 или 3 раза — это звучит уже намного менее драматично.

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


          1. khim
            23.01.2018 14:48

            Вряд ли количество кода для отдельного случая будет отличаться на два порядка) В 2 или 3 раза — это звучит уже намного менее драматично.
            Скорее в 20-30 раз. Многие «правильные приёмы» транслируют 2-3 строки в 40-50 строк легко. Заведение вместо одной простой функйии абстрактного класса, его потомка тому подобного (что внедрение зависимости может требовать), фабрики и прочее.

            У меня было пара достаточно чистых экспериментов, когда мой «сложный код» переписывали и когда, наоборот, я переписывал тормозящий «тупой», но «гибкий» код. Разница была где-то между 10x и 100x, а не в «в 2 или 3 раза — это звучит уже намного менее драматично».

            Даже если чтение (до полного понимания) занимает одинаковое время, то лучше это будет простой код, без неоднозначностей и прочих подводных граблей, о которых можно забыть.
            Это — тоже фикция. «Простой» и «гибкий» код я перписывал именно потому, что добавление новых фич занимало слишком много времени. Так как там было порядка 100'000 строк кода, то «до полного понимания» дело не доходило — в нём просто было слишком много всего, чтобы можно было его полностью понять и все ньюансы запомнить. У его замены примерно на 5'000 кода была другая проблема — без полного понимания того, как разные части взаимодействуют между собой там было невозможно поправить фактически ничего. Но натурный эксперимент показал, что за неделю человек в этом разбирается и может менять то, что нужно менять добавляя 2-3 строки тут и там.

            Такие дела.

            Впрочем есть одно но: если размер компонента таков, что его при любом написании нельзя будет полностью уместить в одной голове — тогда, наверное, «тупой код» лучше.


            1. Free_ze
              23.01.2018 15:09

              Гибкость — это уже другая характеристика, которая напрямую к сабжу не относится. Но даже в этом контексте, обобщенные алгоритмы по определению призваны сократить объем кода.

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

              Хороший код позволяет оперировать абстракциями, без необходимости полностью понимать и запоминать все нюансы реализации.


  1. reci
    22.01.2018 14:15

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


    1. Mendel
      22.01.2018 14:26

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


  1. Psionic
    22.01.2018 14:49
    +1

    Как давно я не видел на хабре кодоснобских постов.
    А как хотелось бы чтоб они вообще не появлялись.
    -Почему у тебя код такой не гибкий — вдруг понадобится Х?
    Через три дня к другой задачи
    -Почему у тебя в коде абстрации?
    -вдруг понадобится Х
    -А МЫ НЕ ПИШЕМ КОД ПО ПРИНЦИПУ: ВДРУГ ВОЙНА, А Я УСТАВШИЙ, УБЕРИ РАЗ БЕЗ НИХ СЕЙЧАС МОЖНО ОБОЙТИСЬ.


    1. Ogra
      22.01.2018 15:01

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


  1. sydorenko-vd
    22.01.2018 14:54
    +1

    Как-то скопипастил код в проект со stackoverflow, а лид говорит:


    зачем скопипастил? Я же вижу, не твой почерк.


  1. SergeyGalanin Автор
    22.01.2018 15:13

    Отвечу сразу на несколько каментов выше.

    В оригинале было «Dumb Code». Причём, было не у автора, а у Гетца. «Простой», на мой взгляд, здесь не подходит по смыслу. Возможно, было бы лучше «код для тупых» или «код как для тупых» — что-то в этом духе.

    И ещё не подходит потому, что «Write Dumb Code» — это, по сути, фраза-провокация, вброс. И сам блог-пост Скотта — это тоже по большей части провокация. В нём нет ничего нового, и никакой темы в нём не раскрывается. Всю суть текста можно свести к одной фразе:

    «Код — это общение между людьми и инструкции для компьютера, но значительно больше первое, чем второе»,

    которая, фактически, является перефразированной цитатой Кента Бека. Цель поста — в очередной раз привлечь внимание к злободневной проблеме. И попытка удалась, что тут скажешь. Скотт — красафчег!

    А если нужна информация по теме простого и понятного кода, то есть хорошие книжки. Только они длинные.


    1. zif
      22.01.2018 16:44

      И какие, по вашему мнению, книги по этой теме хорошие?


      1. SergeyGalanin Автор
        22.01.2018 16:47
        +1

        Мне «Чистый код» Дядюшки Боба нравится, например. Как раз сейчас у меня на рабочем столе лежит.


        1. blackfox_temiks_st
          23.01.2018 14:18

          Это случайно не тот, который советует именовать интерфейсы без I?.. Могу ошибаться.


          1. SergeyGalanin Автор
            23.01.2018 14:21

            Он самый. Глава 2 «Содержательные имена», пункт «Интерфейсы и реализации».


            1. Neikist
              23.01.2018 14:29
              +1

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


              1. VolCh
                23.01.2018 16:56

                А зачем ставить букву I? Я в курсе про венгерскую нотацию, если что. Но когда при чтении кода нам жизненно важно знать, что это интерфейс, а не класс (может быть абстрактный без одного конкретного метода и свойства)?


                1. Neikist
                  23.01.2018 20:46

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


                  1. VolCh
                    23.01.2018 22:00

                    Так я не пойму, зачем их при чтении выделять вообще. Ладно при написании, заметить что написал new ICountable на секунду раньше, чем об этом сообщит IDE. Но читающему-то какая разница?


                  1. Free_ze
                    24.01.2018 00:31

                    А зачем вообще их выделять при чтении?


                    1. DistortNeo
                      24.01.2018 00:42

                      Бывает полезно при чтении API: некоторые функции на вход принимают абстрактные классы, а не интерфейсы.


                      1. Free_ze
                        24.01.2018 00:49

                        И какая разница для юзера? Тому, и другому нужна реализация.


                        1. DistortNeo
                          24.01.2018 01:29

                          Большая. Первый случай накладывает ограничение на базовый класс.


                          1. VolCh
                            24.01.2018 09:14

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


                          1. Free_ze
                            24.01.2018 09:36

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


                            1. sumanai
                              24.01.2018 15:36

                              А вы не думали, что множественное наследование запрещают специально, как раз чтобы не было запутанного кода с неочевидным поведением?


                              1. Free_ze
                                24.01.2018 15:47

                                Ну так пишите очевидный код, чтобы он не был запутанным, в чем беда?)

                                Конкретно это решение было не из самых удачных: в C# и Java появляются костыли для обхода этого органичения — внедрение реализации в интерфейсы теми или иными путями.


                                1. sumanai
                                  24.01.2018 16:32

                                  Ну так пишите очевидный код, чтобы он не был запутанным, в чем беда?)

                                  Я то пишу. Но я не один во Вселенной, и есть куча народа, у которых само наличие возможности написать криво вызывает незримый зуд это сделать.
                                  Конкретно это решение было не из самых удачных: в C# и Java появляются костыли для обхода этого органичения — внедрение реализации в интерфейсы теми или иными путями.

                                  А в PHP есть множественное наследование интерфейсов и примеси для реализации, поэтому проблема не в запрете множественного наследия, а в отсутствии более простой и безопасной альтернативы.


                                  1. Free_ze
                                    24.01.2018 16:47

                                    Я то пишу. Но я не один во Вселенной, и есть куча народа
                                    Ничего против этого не имею. Но ограничение на наследование от этого менее унылым не становится.

                                    А в PHP есть… примеси
                                    «A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.»
                                    Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.


                                    1. sumanai
                                      24.01.2018 16:53
                                      +1

                                      Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.

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


                                      1. Free_ze
                                        24.01.2018 17:07
                                        -1

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


                                        1. VolCh
                                          25.01.2018 09:47

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


                                          1. Free_ze
                                            25.01.2018 11:36

                                            Выглядит так, будто вы меня в чем-то поправили)

                                            Дизайнеры языка редкую сложную проблему сделали насущной (ох, как же бестолковые интервьюеры любят мучить новичков-кандидатов этой разницей интерфейсов и абстрактных классов), а элегантную замену функциональности так и не придумали: в C# — внезапно в пространстве имен появляется какой-то сторонний класс, который докидывает функциональности другой сущности. В PHP было extends и implements, а теперь еще и какое-то use, с похожей, но различной семантикой. Это ли красиво и понятно?


                                    1. DistortNeo
                                      24.01.2018 19:35

                                      Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.

                                      Методы-расширения — это следствие отсутствия в этих языках шаблонов времени компиляции. Если я объявляю дженерик класс, а затем хочу определить метод только для конкретных типов, то приходится писать расширение.


                                1. DistortNeo
                                  24.01.2018 19:32

                                  Я тоже раньше считал это недостатком, когда писал на С++. Сейчас уже не считаю, потому что ни разу не столкнулся со случаем, когда множественное наследование было бы полезно и не усложняло бы код.

                                  Один базовый класс + множественное наследование интерфейсов имеет преимущество в виде нулевого оверхеда по памяти. Вы можете навесить хоть 10 интерфейсов — занимаемый объектом объём памяти не увеличится. А вот в C++ будет по +24 байта на каждый абстрактный класс без полей из-за необходимости использования виртуального наследования. [*]

                                  Плюс интерфейсы в C# больше похожи на концепты, чем на классы. У них есть маленькая, но очень приятная фишка — возможность реализации интерфейса базовым классом:

                                  class Base
                                  {
                                      public void Foo() {}
                                  }
                                  
                                  interface IFoo
                                  {
                                      void Foo();
                                  }
                                  
                                  class Derived: Base, IFoo
                                  {    
                                  }


                                  В C++ так сделать, увы, не получится.

                                  [*] На самом деле, C++ Builder умеет оптимизировать такие вещи и не плодить vtable без нужды. Но вот gcc/MSVC/clang так не делают.


                                  1. Chaos_Optima
                                    24.01.2018 20:00

                                    А вот в C++ будет по +24 байта на каждый абстрактный класс без полей из-за необходимости использования виртуального наследования.

                                    Не будет. Реализация полиморфизма вообще не регулируется стандартом, обычно в классе пишется просто указатель на vtable и всё. Исключением может являться например ромбовидное наследование.
                                    Пруф.


                                    1. DistortNeo
                                      24.01.2018 20:41

                                      У вас пример кривой — вы вообще динамический полиморфизм не используете. Вот правильный: cpp.sh/93fd7

                                      Почему наследование интерфейсом должно быть виртуальным, надеюсь, не нужно пояснять?

                                      А вот тут изначальное моё исследование: habrahabr.ru/post/304492/#comment_9684026

                                      Да, ошибся. Но +8 байт на 1 подобный «интерфейс» — все равно много.


                                      1. Chaos_Optima
                                        25.01.2018 13:35

                                        Почему наследование интерфейсом должно быть виртуальным, надеюсь, не нужно пояснять?

                                        эм… нужно. Я понимаю если бы базовый класс был бы с данными, да тогда там нужно виртуальное наследование, но вот для интерфейса совсем непонятно, уменьшить количество вызовов деструкторов интерфейса? Они и так практически всегда без реализации. Виртуальное наследование нужно исключительно в ромбовидных схемах, когда общий предок имеет данные а не просто когда он интерфейс. Бездумно использовать виртуалку так себе решение.


                1. DistortNeo
                  24.01.2018 00:40

                  Вообще говоря, причин подобных правил именования идентификаторов, как я понимаю, две: недопущение конфликтов имён и повышение эффективности читаемости/писаемости кода.

                  Последний аргумент уже не так актуален с развитием IDE и современных языков и методологий программирования. Поэтому я до сих пор не понимаю людей, которые ставят префиксы/суффиксы для полей-членов классов — это же дико неудобно и нисколько не повышает читаемость.

                  IDE может выделять разными цветами классы, структуры, интерфейсы, а также различать локальные, статические переменные, аргументы и т.д. И если первым функционалом я активно пользуюсь (очень удобно в C# сразу видеть, что тип является структурой), то вторым — нет, выделение переменных разным цветом — это информационный шум.

                  А вот конфликты имён никуда не делись. В том же C/C++ есть макросы, которые нехило всё засирают.

                  Что касается интерфейсов: лично я всегда пишу название с буквы I. Потому что я сначала придумываю интерфейс (что я хочу получить), а уже потом думаю, как буду его реализовывать. Да и вообще полезно иметь много мелких интерфейсов — SOLID, всё такое. И иногда получается, что имя класса, реализующего интерфейс, совпадает с именем интерфейса, но без I. Потому я и называю интерфейсы всегда с I.


                  1. blackfox_temiks_st
                    24.01.2018 08:43

                    несомненно, именование приватных полей типа "_field" вызывает странные ощущения и легкое раздражение.
                    Что касается интерфейсов, то да, имя класса может совпадать с именем интерфейса, так что начинать именовать интерфейсы всегда стоит с I.

                    Так же при такой записи, сразу понятно что ILabelDetetor — это интерфейс.

                     switch(labelType)
                                {
                                    case LabelType.EveryDay:
                                        ILabelDetector = new RedLabelDetector();
                                        break;
                                    case LabelType.White:
                                        ILabelDetector = new WhiteLabelDetector();
                                        break;
                                    case LabelType.Special:
                                        ILabelDetector = new SpecialLabelDetector();
                                        break;
                                    case LabelType.FlyBuy:
                                        ILabelDetector = new FlyBuyLabelDetector();
                                        break;
                                    case LabelType.New:
                                        ILabelDetector = new NewLabelDetector();
                                        break;
                                    default:
                                        return null;
                                }
                    
                    return ILabelDetector.DetectLabel(result, bitmap, googleORM);
                    


                    PS: я новичок в программирование (1 год практики в компании), так что код приведенный выше может казаться неправильным и непонятным.


                    1. lair
                      24.01.2018 09:40
                      +1

                      Ну этот код и нарушает как минимум два принятых нынче правила именования: (а) не надо именовать переменную по ее типу и (б) локальные переменные именуются с маленькой буквы.


                      А все потому, что, на самом деле, низачем не надо знать, что локальная переменная labelDetectorимеет тип интерфейса.


                      1. blackfox_temiks_st
                        24.01.2018 11:12

                        Уточню немного.
                        (a) вы про bitmap? если нет, то прошу указать на место.
                        (b) ILabelDetector? в других местах вроде все ок.


                        1. lair
                          24.01.2018 11:29

                          Нет, я про ILabelDetector = new RedLabelDetector();. Эта строчка читается как "давайте присвоим новый экземпляр интерфейсу (не переменной)".


                          1. blackfox_temiks_st
                            24.01.2018 12:07

                            хм, а вы правы, на задумывался об этом, побежал менять.


                    1. VolCh
                      24.01.2018 10:01
                      +1

                      Чем тут ухудшиться читаемость, если заменить на labelDetector? Как по мне так только улучшится. И вообще пример на переменную, а не на имя класса/интерфейса.


                      1. blackfox_temiks_st
                        24.01.2018 14:59

                        да, мне уже указали на это выше, за что ему спасибо.


                    1. Free_ze
                      24.01.2018 11:38

                      Хотели как лучше, а получилась венгерская нотация) Будь здесь LabelDetector базовым классом классом, то ничего бы не изменилось бы.


                  1. VolCh
                    24.01.2018 09:18

                    Если где-то очень важно избежать конфликта имён или явно показать, что это интерфейс, то лучше добавить суффикс Interface, по-моему, чем префикс I. Хотя бы потому, что читатели кода могут и не знать, что вы используете I для интерфейса, особенно если это явно не указано в ваших стайлгайдах.


                    1. Deosis
                      24.01.2018 12:05

                      99% .Net использует префикс I для интерфейса, поэтому не стоит усложнять жизнь себе или другим, отступая от такого соглашения.


                      1. Free_ze
                        24.01.2018 12:07

                        99% процентов джавистов не используют префикс «I» и живут же как-то!


                        1. ad1Dima
                          24.01.2018 12:15

                          Придумывая как составить прилагательное или обобщить существительное.


                          1. Free_ze
                            24.01.2018 12:21

                            Просто не ставят префикс.


                            1. ad1Dima
                              24.01.2018 12:23
                              +1

                              И ставят суффикс Impl у реализации, ага. (будто это лучше)


                              1. Free_ze
                                24.01.2018 12:27

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


                              1. VolCh
                                24.01.2018 13:31

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


                                В любом случае суффикс Interface или Impl более говорящий чем префикс I.


                                1. ad1Dima
                                  24.01.2018 13:40

                                  В любом случае суффикс Interface или Impl более говорящий чем префикс I
                                  С учетом, что префик I, является общепринятым обозначением — никакой разницы.
                                  А о чем говорит Impl в абстрактном классе SocketImpl мне не очень понятно.

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


                                  1. VolCh
                                    24.01.2018 14:08

                                    С учетом, что префик I, является общепринятым обозначением — никакой разницы.

                                    Судя по треду, он является общепринятым лишь в некоторых экосистемах типа .NET и C++, а в других таковым не является.


                                    Отказ от префикса решает некоторые проблемы и создаёт новые. Введение префикса также решает некоторые проблемы и создаёт новые. Судя по всему, в обоих случаях решенные и создаваемые проблемы друг друга нивелируют. Хотя, может, в одном из случаев уже есть понимание, что создаваемых проблем больше, но груз обратной совместимости держит. Может пока держит.


                                    1. ad1Dima
                                      24.01.2018 14:14
                                      +1

                                      Я о том и говорю, в общем-то. Поработав с обоими подходами я считаю что это все вкусовщина и делать нужно так, как принято в платформе.

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

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


                                  1. DistortNeo
                                    24.01.2018 19:41

                                    А о чем говорит Impl в абстрактном классе SocketImpl мне не очень понятно.

                                    Ага. Я бы назвал SocketBase


                      1. sumanai
                        24.01.2018 15:40

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


                    1. DistortNeo
                      24.01.2018 19:40

                      В .NET принят префикс, в Java — суффикс. Факт в том, что он есть, и не надо из этого устраивать холивар.


                      В защиту префикса скажу, что слово "Interface" может использоваться при программировании UI. А вот две заглавные согласные буквы в начале имени интерфейса — это уже более редкий случай.


                  1. Chaos_Optima
                    24.01.2018 12:19

                    Поэтому я до сих пор не понимаю людей, которые ставят префиксы/суффиксы для полей-членов классов — это же дико неудобно и нисколько не повышает читаемость.

                    Тут несколько причин, не всё ограничивается только IDE. Даже с IDE для понимания это удобнее, когда читаешь m_ сразу понимаешь что это поле класса. У разных разработчиков подсветка может быть настроена по разному. Ну и что по моему мнению наиболее удобно так это то что при наборе m_ подсказчик подсказывает именно поля а не всё подряд. Тоже самое с интерфейсами, набираешь I и тебе показываются только интерфейсы.


                    1. DistortNeo
                      24.01.2018 19:47

                      Даже с IDE для понимания это удобнее, когда читаешь m_ сразу понимаешь что это поле класса.

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


                      Да и просто набор лишних символов, не относящихся к бизнес-логике, раздражает.


                      У разных разработчиков подсветка может быть настроена по разному.

                      Так дайте разработчику выбор. Лично я такие вещи не подсвечиваю, потому что не вижу в этом смысла. Кому надо — сами настроят IDE.


                      при наборе m_ подсказчик подсказывает именно поля а не всё подряд

                      Ага, и поэтому в Google C++ coding style guide для членов класса вообще предлагается использовать суффикс (_).


                1. Ogra
                  24.01.2018 09:29
                  +1

                  А зачем ставить букву I?


                  Ну, для С++ я вижу смысл — там нет такой языковой конструкции, как интерфейс. Отделить интерфейсы от классов через стайлгайд — нормальная идея.
                  Для языков, где интерфейсы есть — бесполезная буква.


                  1. ad1Dima
                    24.01.2018 10:24

                    тем не менее в C# принято ставить I.


              1. SergeyGalanin Автор
                24.01.2018 10:06

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


  1. Legion21
    22.01.2018 16:10
    +1

    Хм… сплошная критика… ради чего?) Пишите код как хотите…


    1. Mendel
      22.01.2018 16:24

      Да, да, пишите как хотите, только адрес свой указать не забудьте. Бензопила у меня есть, справку купим…


      1. Psionic
        23.01.2018 16:59

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


        1. Mendel
          24.01.2018 13:26

          Обычно это проходит.
          Достаточно поработать пару лет над проектом с парой дюжин разработчиков, когда в сфере твоей ответственности работает команда из пяти человек, а с остальными коммуникации редкие… Сразу понимаешь что дробовик купил не зря. Только с целью ошибся...)


          1. Psionic
            24.01.2018 14:27

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


  1. FlameArt
    22.01.2018 16:44

    Это 4 разных проблемы:

    1. Люди стараются выглядеть умнее, чем они есть на самом деле. И код тут не причём, это лишь желание уважения (либо подросток до 25), которое можно создать здоровой атмосферой в команде.
    2. Люди хотят развиваться и пользоваться инструментами, о крутости которых слышали от других. Люди хотят разрабатывать свои фичи. И это нормально, можно лишь создать культуру такого развития, чтобы проекты не превращались в хаос, не затягивались, а новые идеи были документированы и обсуждены.
    3. Люди не понимают к чему приведут их решения с архитектурной точки зрения. Из-за недостатка опыта, который не так страшен, намного страшнее когда не понимают как их кусочек будет лежать в общей архитектуре, для чего они его делают и как в целом тут всё работает. Такое я наблюдал регулярно: не понимающий телеги прогер будет делать колесо с набором грязи, которую он считает правильной.
    4. Для долгосрочного развития проекта надо писать понятный код в единой канве. И это тоже не задача кодеров, культуру этого (и инструменты) должны выстраивать тимлиды и управленцы, вплоть до первого лица.


    1. nporaMep
      23.01.2018 11:01
      +2

      По пункту 3 очень часто замечал, что архитекторы наархитекторили чего-то без описания и потом не могут сами объяснить как же вкрячить вот это то что надо для этой задачи.
      Или архитекторы просто сваливают, не оставив описания какую ж архитектуру они тут нагородили.
      Или архитекторы архитектят начитавшись чего-нибудь и решивших применить последнее прочитанное на вот этом проекте сами не до конца понимая всех узких мест.


  1. emacsway
    22.01.2018 16:44

    Понять эту статью можно только с позиции опыта. Могу ее добавить цитаты известных авторитетов на тему простоты и проблемы умных людей. Еще по теме статья М.Фаулера Is Design Dead?.


  1. encore-show
    22.01.2018 16:44

    Согласен с тем что, простой код понятнее, особенно когда проект командный. Но ведь некоторые к этому относятся как к искусству. Во времена ограниченных ресурсов люди придумывали новые обходы и как оптимизировать, а сейчас «Если код отрабатывает долго, то, давайте увеличим железо». Считаю что должен быть баланс)


  1. MaximM2017
    22.01.2018 16:44
    -1

    В комментариях часто проскакивает слово «простой». Может можно заменить определение «тупой» на «простой»?


  1. itwaze
    22.01.2018 16:45
    -1

    Познавательно).


  1. artemmityushov
    22.01.2018 16:45

    Был у меня один коллега который придерживался двух принципов:
    1. Если есть функцию то ее нужно использовать не думая нужна она или нет нужно придумать место ее использования.
    2. Если у языка есть возможность то ее нужно обязательно использовать.

    В результате все его проекты были как учебник по языку, использовалось все, и он этим безумно гордился, только вот никак не мог понять почему его каждые пару лет просили покинуть компании т.к. на любые доработки его старого кода нужно были минимум в 3-4 раза больше ресурсов чем у всех остальных разработчиков.


  1. yurec_bond
    22.01.2018 17:15
    +1

    «Любой дурак может писать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям.»


    Совершенно согласен с этой фразой и в связи с этим не понимаю как JavaScript приобрел такую популярность?
    Из-за отсутствия типизации совершенно не возможно понять кто кого вызывает и как код работает.
    И не смотря на несильно удачные попытки создать IDE которое поможет навегировать и рефакторить код, самым лучшим инструментом остается Search/Search-Replace


    1. VolCh
      22.01.2018 17:44
      +1

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


  1. AlexPu
    22.01.2018 17:34

    >>я ненавижу термин «сениор» и называю себя просто разработчиком ПО

    Фигасе… а думал я один такой… в свои 48 лет при стаже в отрасли более 25-ти лет (и именно разработчиком), я могу себе позволить не считаться с титулами…

    >>«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».

    Опять-же я думал, что это я придумал лет 15 назад наверное (но я никогда в такого никому не говорил. Ну… может по пьяне сболтнул кому?)


    1. VolCh
      22.01.2018 17:47

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


      1. kost
        23.01.2018 04:44

        Senior — это «старший» по-русски.


  1. ChePeter
    22.01.2018 18:37
    -1

    «Тупой код», как правило, написан кем то другим.


  1. Nondv
    22.01.2018 20:47

    Милый пост.
    Спасибо за перевод!


    P.S. и добро пожаловать на хабрахабр;D (песочница)


  1. aPiks
    22.01.2018 21:54

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


  1. no1s1a
    22.01.2018 21:54

    Чисто на ментальном уровне мне нравиться эта мысль. Простой и понятный код — это произведение искусства. Иногда читаешь код, вроде понятно как это работает, а вроде ??????????


  1. Barracuda120
    22.01.2018 21:54

    Keep it simple.


  1. marf1k
    22.01.2018 21:54

    У нас практика — не писать комменты, только документацию.
    Почти не встречаются случаи — отковенная муть но я откомментировал и моя совесть чиста


  1. ap_gubarev
    25.01.2018 12:17

    Опасная фраза «тупой код». Тупые воспримут ее буквально и будут писать лапшеговнокод