Pull to refresh

Comments 407

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

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;
}
}
Нет, недостаточно. Можно ещё тупее:

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

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

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

а если symbol=='Ж'? В первом случае такого вопроса просто не возникает, здесь же придется смотреть контекст
А если кодировка отличная от 7-битной? И язык, например, казахский? Не нужно строить лисапедов без необходимости.
Стандарт с++ не гарантирует порядок символов английского алфавита в char. Поэтому, чтобы проверить является ли символ буквой нужно пользоваться стандартной функцией std::isalpha.
bool isBukva (char symbol)
{
    return std::isalpha(symbol);
}
Потому как легко пропустить одну букву, и функция будет вести себя не так, как ей положено.

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

Или так if (std::tolower(symbol) != std::toupper(symbol))
UFO just landed and posted this here

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

Возможно это вывод в цикле в текстовый файл, программа из трех строчек… а уже написали ))
Лень — плохое качество для программиста, говорю тебе как синьёр.
Пфф. Лень — двигатель прогресса. Программисту лень что-то делать, и он это дело оптимизирует.
Лень — двигатель прогресса
Прогресс выводится на орбиту с помощью РН серии Союз-У, двигателями которого являются РД-117, РД-118 и РД-0110.
Теперь я думаю почему индусы не сениоры
Возможно, когда-то давно, во времена EBCDIC и других альтернативных (не ASCII) кодировок, это и был тот самый «тупой», зато платформо-независимый код.
Как показывает моя практика, времена EBCDIC еще не прошли =)
Не тянешь на синьора. Тупизна кода достаточная, но функция должна возвращать bool, а ты возвращаешь int.
UFO just landed and posted this here
Тем не менее, в этом случае объявление функции подразумевает, что где-то выше по коду было
#define bool int
что в свою очередь, как правило, сопровождается
#define false 0
#define true 1

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

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

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

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

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

Не проблема
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;}}
Так он в экран уместился, но удобнее для чтения не стал.
Разбор аргументов в 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
Ну это ж shell! Какую вы предлагаете альтернативу без bash'измов?

Видите там наверху цикл? Он категорически некорректен (не делает экранирование правильно), но там вполне можно было написать setcmd="set --" ; for arg ; do setcmd="$setcmd \"\$args$i\"", а потом использовать eval. Это если конечно данный set вообще нужен, может быть его лучше заменить на что‐то другое — я так далеко не копал.

Это если конечно данный set вообще нужен, может быть его лучше заменить на что‐то другое — я так далеко не копал.
Они в конце exec делают, тут без set в POSIX shell'е не обойтись.

Он категорически некорректен (не делает экранирование правильно), но там вполне можно было написать setcmd="set --" ; for arg ; do setcmd="$setcmd \"\$args$i\"", а потом использовать eval.
Ну вот как раз то место сделано довольно-таки дико. `echo args$i` — это просто пять баллов.

Да, пожалуй. Если они уже всё равно «замарались» в eval, то вариантов особо-то и нет: лучше один раз правильно сформировать команду, чем делать то, что они творят…

В POSIX shell без eval не очень много можно сделать. Я вот как‐то писал парсер --ключей: https://github.com/neovim/neovim/blob/c693afb8ac0aea94bc268f880511d7b7f3710d2c/scripts/pvscheck.sh#L76-L243 (вообще по‐хорошему надо бы в библиотеку оформить), там eval используется аж 19 раз (а вот echo — ровно один, и то в месте, где его вызов соответствует принципу «garbage in — garbage out»).

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

import string
symbol = 'a'
if symbol in string.ascii_letters:
print(«I'm a letter!»)
Перебирать каждый раз массив? Твоё решение ещё хуже приведённого выше.
Видимо, опыт сказывается)
Да, видел такую версию, когда делал перевод. Тут я склонен предположить, что уже в те времена это был известный в узких кругах мем, а Джон просто его процитировал.
В оригинале же упоминался именно Дейв, и ему тоже эту цитату приписывают, так что решил оставить его.
ага, тоже в глаза бросилось
Я часто применяю тернарные операторы, вместо if else, это тупой код? А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость? По-моему нет, но это и понятно, иначе я бы так не писал.
А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость?

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

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

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

Всегда ли проще читать?
var a = cond ? GetVal1() : getVal2();

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


Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.
// 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(); }

На вкус и цвет…
первый всё ещё более читаемый.
UFO just landed and posted this here
Теперь я начинаю понимать авторов, которые пишут:
if (cond) 
{
  return true;
}
else
{
  resturn false;
}

Раньше меня такие конструкции приводили в недоумение…
Им платят за строчки кода. Если бы они просто назвали `cond` читаемым именем не надо было бы городить костыли.
не понимаю вашу мысль. Как это связано с моим комментарием?
Теперь я начинаю понимать авторов, которые пишут:
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;
var result = (someValue == 0 ? true : false);

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


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

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

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

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

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

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

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

(true ? true : false) // -> true
(false ? false : true) // -> true

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

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

OtherMethods();

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 в возврате.

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


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


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

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

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


return cond? true: false;

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


return !!cond;

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

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


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

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


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

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


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


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

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

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

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

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

Даже если типа bool вообще нет, как в C (или уже есть?) и где-то в языке заложено #define true 1, то return cond; в случае когда cond может отказаться равно, например, 2, может приводить к сложно диагностируемым ошибкам
Можно использовать код
return !!cond;
В boost это очень распространено.
if (cond) 
{
    return true;
}
else
{
    return false;
}
Так пишут не для простого кода и читаемости.
А пишут от непонимания даже базовых типов и операторов.
Не всегда. Я, например, так пишу для того, чтобы при дебаге можно было поставить breakpoint на конкретную строчку на конкретное условие. Обычно, правда, в репозиторий это не идет, если ушло — значит, не заметил, поправлю на review.
Естественно, должно быть без фанатизма в плане краткости кода.
Иногда есть смысл в коде с таким ветвлением (хотя в случае дебага можно воспользоваться точкой останова с условием).
Бывает еще, когда ветвление более не только более читаемо, но и более масштабируемо — в том смысле, что может ожидаться, что блоки будут расширены дополнительным кодом.
в случае дебага можно воспользоваться точкой останова с условием
В VC++, к сожалению, скорость дебага от этого падает в несколько раз.
Строго говоря — этот код не идентичен. В случае тернарного оператора в любом случае будут вычислены оба выражения.
Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку
От языка зависит. В плюсах — нет, не позволяет.
В начале треда говорится про Linq. В c# позволяет.

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


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


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

PPS


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

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

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

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

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

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

все дело в привычке. Для простых условий, как по мне, намного проще прочитать однострочный тенарный оператор чем продиратся через четыре строчки if-else
для меня такой вариант намного понятнее
ClosesType closes = isColdOutside ? COAT : TSHIRT

чем
ClosesType closes;
if (isColdOutside)
    closes = COAT
else
    closes = TSHIRT 
Там семантика (обычно) разная: LINQ — query, foreach — command.

Осмысленный выбор читаемость улучшает.
Работал много лет назад с одним парнем. Он писал вот так:
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;


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

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

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;


Вот за использование производной группировки в исходной же переменной стоило бы попинать.
Не заметить в этой простыне = вместо == и искать потом баги — бесценный опыт. Так и становятся сеньорами =)
Ага у нас один индиец таким вот кодом биллинг запортачил. Перепутал == и =. Месяц одним клиентам приходили лишние счета, а другие получали сервис бесплатно. Сеньором он становится будет в другом месте.
UFO just landed and posted this here
Он именно в строку писал. Этот код у меня хранится специально для аргументов про тернарные операторы. :)
Да, так намного лучше.
За написание тернарников в одну строку и без скобок надо сразу лишать печенек.

Тернарная запись, если не читается однозначно — это источник батхерта, особенно при отладке. Не говоря о отладке чужих багов;)
А почему бы не завести какой-нибудь Map<String,String> для такого дела? (не знаю, на каком языке этот код написан; сравнение строк по значению накладывает ряд ограничений, но вариантов все-равно больше одного).
Ну обычно такая конструкция заводится неожиданно. Сперва одно условие, потом два и понеслась. Проще дописать одно условие, чем феншуизироваться с риском ошибки.

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

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

Но, тем не менее, такой код читается. И часто лучше, чем map ;)
можно и так, если грамотно форматировать. Пример из книги «Анализ программного кода на примере проектов 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]

В ёлочку код не всегда читабельнее
UFO just landed and posted this here

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

Наглядность кода стоит усилий.
Ну, если после двоеточия переносить проверку следующего условия на новую строку под предыдущей проверкой, то тоже ничего читается.
Вот нашёл такой кусочек кода (строка длиной 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. Беспокоиться не стоит — это автосгенерённый код. Если выровнять — будет около полсотни вполне красивых строчек.
P.S. Беспокоиться не стоит — это автосгенерённый код.
Я долго не мог понять почему мне новенький разработчик жаловался. Оказалось он читал .min.js
UFO just landed and posted this here
Частенько замена foreach на LINQ действительно ухудшает читаемость, за этим надо внимательно следить…

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


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

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

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

Для начала как хранятся данные о связях? Какие части нам требуется оптимизировать? 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,
}

В большинстве компилируемых ЯП разницы нет. Алсо есть же дженерики и в той же java.

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


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


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


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

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


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


P.S. Не java, а c#, но они очень похожи.

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

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

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

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

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

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


Пример:


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

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

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


let (y, x) = (x, y); // фактически меняет только их имена
                     // но мы можем так делать с разными типами

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

Доки на swap


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


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

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

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

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

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

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

Имелась в виду тождественость ссылок после вызова 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++-то можно! И видно, что ни о какой «тождественности ссылок» речи не идёт.
Проверить «тождественность ссылок» в 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";
}

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


код std::swap
template<typename T> void swap(T& t1, T& t2) {
    T tmp(t1);
    t1=t2;
    t2=tmp;
}
Мне даже немного неловко это писать. Но как это нельзя? А что == делает?
Она сравнивает содержимое ссылок.

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

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

Вы недостаточно знаете 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 (на синенький текст ссылочки на документацию скастовали)

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

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

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

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

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

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

Пример:
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 — нельзя.
Прекрасно: допишите в мой пример свою функцию так, чтобы он компилировался и работал, потом — можно будет поговорить.
допишите в мой пример свою функцию так, чтобы он компилировался и работал
Эта функция уже реализована — 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
Контрольный вопрос: в C для вас есть «передача по ссылке» или нет?

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

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

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

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

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

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

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

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

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


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

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

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

Но и в Java и в C++ для функции, подобной swap мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!
в Java и в C++ для функции, подобной swap мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!
Если мы хотим изменить состояние объекта, то нельзя. Но если мы хотим поменять местами объекты (в чем и кроется смысл swap), то в C++ это делается запросто чистыми синтаксическими средствами, в Java — только неочевидными хаками с целью преодолеть и превозмочь.
Но если мы хотим поменять местами объекты (в чем и кроется смысл 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, определённой для неё. В языке для этого ничего менять не потребуется.
Это понятно, мутаторную форму std::swap для std::basic_string мы условились не использовать.

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

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

Полным аналогом версии из Java в C++ будет следующее
У вас мутабельный объект в стеке. Попробуйте еще раз.
Нет. Нужны ссылки. Союственно «иностранное» название call-by-reference как бы намекает.
Там call-by-reference и происходит) Указатели передаются по ссылке. Только в Java «безопасные указатели» называются ссылками.
Да, но вопрос передачи аргументов по ссылке или значению — он немного ортогонален, хотя в Java они связаны: все обьекты, размещённые в стеке передаются по значению, все обьекты, размещённые в куче — по ссылке.

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

В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на 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». С какого, я извиняюсь, перепугу?
В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на 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;
}

В джаве переменные с объектами — указатели, которые, как и всё остальное передаются по значению, вот что я имею в виду.

В джаве переменные с объектами — указатели, которые, как и всё остальное передаются по значению, вот что я имею в виду.
Передать «ссылку на ссылку» в Java действительно нельзя — но какое это имеет значение? Обьект-то передаётся по ссылке!

P.S. На самом деле в Wikipedia всё описано. Стратегия передачи параметров в Java (JavaScript, Python, далее везде) имеет отдельное имя: Вызов по соиспользованию (call by sharing).

Видимо для того, чтобы, наконец, прекратить эту дискуссию… потому что некоторые языки называют «это» вызов-по-значению, а некоторые вызов-по-ссылке… несмотря на то, что семантика одинакова!
А вы специально в примере используете не такие строки, как в 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...
Я боюсь это становится вопросом терминологии. Можно либо считать, что в Java всё и всегда передаётся по ссылке, но взять «ссылку на ссылку» нельзя, потому что обьекты, размещённые на стеке, недоступны GC, либо разделять обьекты (хранящиеся в куче) и ссылки на них (размещённые на стеке).

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

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


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

у вас была содержательная, а не терминологическая ошибка в картине мира.
Извините, но что-то не так в вашей картине мира, а не в моей. Ибо заявление:
то работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен
это что-то за гранью добра и зла.
В Java так сделать нельзя, что с int, что с Integer.
В Java так сделать нельзя, не потому, что Integer передаётся по значению, а потому что он Immutable. Mutable вариант — это, например int[] с одним элементом.

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

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

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


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

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

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

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

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

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

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

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

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

Только большинству приложений это по барабану.
UFO just landed and posted this here

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

производительность процессоров растет быстрее, чем производительность людей.
В тех 7% где не растет — будет не тупой код, и комментарий «не трогайте тут, выигрываем несколько тактов»
Ну, как совет при оформлении PR в ASP.NET Core, пишут в гайдлайнах избегать Linq в hot-path частях.
Потому что там это имеет смысл. Но подавляющему большинству приложений далеко до ASP.NET Core в плане «горячности» hot path'ов. А каким-то это и вовсе неважно.

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

да и вообще, одна из любимых фраз: простые вещи ломаются реже сложных.
Искусство хорошего именования — часть искусства написания хорошего кода.
Очень важная часть! Naming things and cache coherency are the hardest problems in CS. :-)
А называю это не «тупой», а «скучный» код. Код в котором нет никаких неожиданностей, ничего неочевидного, нет никаких крутых финтов, никаких исключений из правил, никаких нарушений архитектурного шаблона, никаких сложных абстракций, код, который может понять даже человек не знакомый с этим конкретным языком программирования.
код, который может понять даже человек не знакомый с этим конкретным языком программирования
Не к этому ли нужно стремиться?
код, который может понять даже человек не знакомый с этим конкретным языком программирования
Не к этому ли нужно стремиться?

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


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

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


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

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


На Java или C# это выражение читается совершенно однозначно.
(Единственное заметное исключение возможно в C/C++ — языках со слабой статической типизацией — там нужно смотреть особенности приведения чисел к bool или наоборот, но и там выражение будет однозначно читаться без скобок.)


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


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


Статически и динамически типизированные языки — слишком разные, чтобы пытаться писать на них так, чтобы код на обоих языках был в едином Code Style.


Представляете, что будет, если предложить Ruby-, Elixir- или Pythone-разработчикам предложить писать так, чтобы их код быстро и однозначно понимали Java- или C#-разработчики?

Не путайте строгую типизацию с динамической. Для данного выражения нужно знать приоритет операций прежде всего, чтобы знать как оно будет понято транслятором. В языках со строгой типизацией транслятор выдаст ошибку, со слабой — куда-то чего-то приведёт. В языках с сильной статической типизацией ошибку выдаст в компайлтайме, с динамической в рантайме. По крайней мере я не припоминаю языков, в которых приоритет операций менялся бы от типа операндов. Типа, если в x1 > x2 && y1 == y2 у y1 булевый тип, то сначала выполнится &&, а если числовой, то сначала ==.

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

Только написано.


На Java или C# это выражение читается совершенно однозначно.

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

Не однозначно оно читается в любом языке, если не знаешь приоритетов.

Вот это браво!
Если вы не знаете Java, вы не может на ней писать, верно.
И читать вам код на Java тоже не надо, пока не выучите.
Есть случаи, когда необязательные скобки делают код более читаемым и/или надежным (когда приоритет операторов неочевиден даже в рамках языка).

А это вы про кого писали, знающего язык или незнающего?

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

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

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

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

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

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


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


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


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


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

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


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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

Для этого больше подходят локальные функции, и если говорить а C#, то, благо, они в нем появились.
На хабре недавно была очень хорошая статья про локальные функции в C# 7, и оказалось, что они отличаются от лямбд сильнее, чем лично мне казалось ранее.


А лямбда нужна именно как стратегия, расширяющая метод — передаваемая в него извне.
Если на примере C# — то LINQ отличный пример из коробки.
Т.е., как раз лямбда приходит снаружи.

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

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

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

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

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

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

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

У локальных функций есть замыкания, и для реализации перечисленных вами требований больше подходят именно локальные функции.
Лямбды для другого.
Если локальных функций нет, то в качестве замены приходится использовать лямбды.
Подробнее по локальные функции в C# 7 здесь.

То есть вместо, например, лямбды x => x * k вы создадите целую локальную функцию? Можете это аргументировать?

Посмотрите контекст подветки.
Участник Chaos_Optima говорил о случае, когда внутри метода нам нужно создать подметод, работающий только в контексте основного метода.


Раньше для эмуляции локального метода использовались лямбды (лямбды, по сути, использовались при этом не по назначению):


    class Program
    {
        static void Main(string[] args)
        {
            Foo();
        }

        static void Foo()
        {
            int x = 0;
            Action subFoo = () => x++;
            subFoo();
            Console.WriteLine(x);
            Console.ReadLine();
        }
    }

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


    class Program
    {
        static void Main(string[] args)
        {
            Foo();
        }

        static void Foo()
        {
            int x = 0;
            SubFoo();
            Console.WriteLine(x);
            Console.ReadLine();

            void SubFoo() => x++;
        }
    }

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


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


В реальном примере, скорее всего, локальная функция должна делать что-то посложнее, чем x++.
Хотя… если такая локальная функция вызывается более одного раза, и потенциально ее код может расширяться, то и ради x++ можно создать локальную функцию.

Конкретно в вашем случае можно написать сильно проще:


    class Program
    {
        static void Main(string[] args)
        {
            Foo();
        }

        static void Foo()
        {
            int x = 0;
            x++;
            Console.WriteLine(x);
            Console.ReadLine();
        }
    }

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


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


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

При этом нельзя захватывать scoped variables.


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

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


Вот было просто:


var result = data.ToDictionary(x => x.Key, x => x.Value1 + x.Value2);

А по-вашему надо писать:


var result = data.ToDictionary(DataKeySelector, DataValueSumSelector);

TypeOfKey DataKeySelector(DataType item)
{
    return item.Key;
}

TypeOfValue DataValueSumSelector(DataType item)
{
    return item.Value1 + item.Value2;
}
Так ведь все дело как раз в простоте, и лично я только за. Если все можно записать просто, зачем городить коробочное ПО с собственным классом с кучей методов и паттернов, известных только в академической среде? Такие вот якобы показатели своей крутости и отбивают у новичков тягу программирования, и даже у опытных создают сложности при чтении кода, когда нужно еще и в зависимостях копаться дабы понять что и откуда вызывается, причем через десяток сторонних методов, ну типа для удобства и декомпозиции. В том-то и проблема.

Ну, грубо говоря, если я вижу код типа


public approveContract(string contractId, ContractRepository contractRepo): void
{
  Contract contract = contractRepo.get(new ContractId(contractId));
  contract.approve();
}

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

От, примеры кода лучше всего иллюстрируют точку зрения, действительно… Вот, то, о чем я и говорю, это именно то, что я и подразумевал. Тут даже моя девушка разберется :/ Но позвольте-с, а Вы что предлагаете в этом случае? Пихать реализацию этих магических методов в этот же код? Понятно, что какие-либо библиотеки в данном случае предпочтительнее, однако часто они не решают и малой доли поставленных задач, значит нужно городить свои эти самые Contracts. Да и не стоит пользовать библиотеки, «известные только в академической среде», многие известные продукты имеют милионную базу пользователей, а иногда и комьюнити больше чем имеет сам язык, для которого она написана. Или я что-то не так понял?

Я уже точно мало понимаю о чём мы :)


Я о том, что подобный код даёт мне основания предполагать следующее:


  • Класс Contract реализует бизнес-логику какого-то договора, причём, скорее всего, достаточно инкапсулированную.
  • Метод approve класса Contract осуществляет утверждение договора
  • Класс ContractRepository реализует паттерн Repository для инстансов класса Contract, представляя постоянное хранилище данных (СУБД, внешний сервис, файлы, память) в виде коллекции. Причём на данном уровне мне абсолютно всё равно, какого вида хранилище, и как реализован класс. Он может быть реализован с помощью сторонней бибилиотеки, которая на порядок больше остального приложения, ещё большей самописной реализацией, а может несколько строк.
  • ContractId скорее всего реализует паттерн ValueObject
  • необходимости вызывать тут метод типа Contract.save нет, даже если он в классе имеется как унаследованный от базового класса какой-то библиотеки. Это при условии, что я знаю, что код рабочий.

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

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

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

Все же мне до сих пор навязчиво кажется, что что-то все еще уходит от моего понимания.

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

Подождите, а код, который Вы привели выше, для Вас он простой? Вот мне и интересно, что если нет — то почему? Для меня он прост, и я не вижу смысла усложнять его академическими паттернами. Просто зачем? Сомневаюсь, что эти паттерны позволят работать с теми же документами :/

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


  • Entity
  • Repository
  • ValueObject
    и несколько принципов SOLID, в частности принцип единственной ответственности и принцип инверсии зависимостей
О, ну дак это другое дело, просто у меня поэтому разрыв шаблона и случился, ибо вначале речь шла о том, что простой код не имеет лябд и ФП, а значит некошерный и вообще, вот я и распинался несколько дней, доказывая, почему код должен быть простым, а оказалось что за лямбды и «академические паттерны» топили вовсе не Вы :/
Я посчитал, что Вы имеете ввиду, что это для Вас пример сложного кода. Как неловко-то, господиисусе.
Сложность метода измеряется количеством его обязанностей. Данный пример, конечно же прост. И во многом благодаря паттерну Repository. Каждый кто хоть раз в жизни прочитал PoEAA, знает, что этот паттерн отвечает за сокрытие деталей реализации доступа к данным. Кроме того, мне не нужно смотреть его реализации для того чтобы понять, что никаких дополнительных обязанностей (проверку прав и т.п.) он не выполняет.
Да, это я уже понял, благодарю, просто по фразе «у меня есть основания полагать, что приложение написано с использованием „кучей методов и паттернов, известных только в академической среде“», я считал, что их использование — зло, ибо они известны только в этой самой «академической среде», вот я считал, что коллега считает этот код сложным)
Все верно. Если интерфейсы ни о чем не говорят, и разработчик вынужден подсматривать реализацию, то проектирование плохое. Программа должна читаться, а не пониматься. Просто потому, что в процессе написания кода разработчики 90% времени читают программу, и плохая читаемость на 90% влияет на темпы разработки.
Код в котором нет никаких неожиданностей, ничего неочевидного, нет никаких крутых финтов, никаких исключений из правил, никаких нарушений архитектурного шаблона
Да и вообще, с этим нужно аккуратнее. Программист должен думать не о том, чтобы показать работодателю какой он крутой, а думать об оптимальности. Можно и гвозди кувалдой забивать, ибо из-за своего веса с одной стороны она более эффективна, только вот устанет человек быстрее чем забьет гвоздь. Плюс скорее всего раздербанит и поделку и сам гвоздь. А можно взять молоток. Он легкий и простой, и создан специально для этого. Другой вопрос что можно и им пальцы отбить, но это уже и вовсе зависит от подготовки джуниора, все-таки на работу не профанов обычно берут.

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

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

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

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

Сеньор-помидор там или нет. Но реально замечаю, что за новичками, или точнее сказать индусами или вьетнамцами, основной способ улучшения или даже исправление их кода, это в основном удаление по Ctrl+l.
Другая их большая проблема, это совершенное непонимание, что будет с их кодом написанном в одном таске буквально на следующий день в их же следующем таске. Простейшее решение для них, это копипаста и последующее исправление всех (или нет) копипаст в случае изменений.
UFO just landed and posted this here
А потом это все выходит в «нам на приложение, которое читает rss нужно 5 серверов».

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

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


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


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

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

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

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

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

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

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

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

Такие дела.

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

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

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

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


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

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

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

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

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

А если нужна информация по теме простого и понятного кода, то есть хорошие книжки. Только они длинные.
И какие, по вашему мнению, книги по этой теме хорошие?
Мне «Чистый код» Дядюшки Боба нравится, например. Как раз сейчас у меня на рабочем столе лежит.
Это случайно не тот, который советует именовать интерфейсы без I?.. Могу ошибаться.
Он самый. Глава 2 «Содержательные имена», пункт «Интерфейсы и реализации».
Черт, серьезно? Когда читал внимания не обратил, пусть даже в используемой мной платформе нет ООП, но странно что я это пропустил. А там нет уточнения что это для случаев когда интерфейс — это элемент языка, или что то в таком духе (к сожалению книга не под рукой сейчас)? Просто этот пункт странным кажется.

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

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

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

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

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

Это искусственное ограничение лишь унылых языков, в которых запрещено множественное наследование. Для понимания алгоритма это не играет никакой роли.
UFO just landed and posted this here
Ну так пишите очевидный код, чтобы он не был запутанным, в чем беда?)

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

А в 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.
UFO just landed and posted this here
Введение новых конструкций в язык для того, чтобы исправлять достаточно редкий кейс не особо-то повышает прозрачность.

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

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

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

Семантика у них вполне прозрачная и соответствует разным ситуациям:


  • implements — добавляет только объявление методов, обязывая этим класс сделать свою реализацию, с возможностью передавать реализацию туда где затребован интерфейс
  • extends — добавляет существующую реализацию из родительского класса, с возможностью передавать дочернюю реализацию туда где затребован интерфейс родителя
  • use — добавляет существующую реализацию, без возможности ее требовать и передавать, практически аналог include.
Цель-то у всего этого одна — вынести общий код. Но вместо одного инструмента отросло аж три. Причем, третий нужен, чтобы достигать цель вопреки ограничениям второго.

Здесь можно рассуждать сколько угодно, что это был изначальный план, но объективный факт: эти вот миксины, методы расширения и дефолтные реализации методов в интерфейсах — это результат эволюции языков, их не было изначально, но они понадобились. Поэтому я и называю это фиксом дизайна или даже костылями, акцентируясь на том, что они усложняют синтаксис.
Нет. Третий нужен, чтобы достигать цель без привлечения проблем второго. Проблемы связаны с тем, что копируются и интерфейс и реализация. Первый нужен чтобы копировать только интерфейс, третий только реализацию. Отдельный инструмент для каждой задачи.
И вы не с той стороны смотрите. Цель не вынести общий код из одного места в другое, а удобное использование этого кода в третьем, включая контроль типов. Если вы не указываете типы переменных, полагаясь на возможность языка вызвать любой метод по названию, то да, особой разницы нет.
Третий нужен, чтобы достигать цель без привлечения проблем второго.
Так и я о том же и говорю) Только вы видите в этом отдельную подзадачу для которой прям необходима отдельная синтаксическая конструкция, а я вижу частный случай привычного решения.
Единый синтаксис для интерфейсного наследования и обычного.
Множественное наследование что-ли? Под словами «проблемы второго» я имел в виду не отсутствие множественного extends, а наоборот, проблемы этого множественного extends, если бы оно было разрешено. Поэтому нет, это разные задачи, и логично что для них есть разные синтаксические конструкции.
Тут не только костыльный вариант привычного решения, но и попытка оградить программистов от дополнительной возможности выстрелить себе в ногу.
При множественном наследовании есть проблема «кто чей родитель». Да, спокойно можно сделать решение в духе «кто первый тот и папа», и это прекрасно работает. Если это помнить, знать, и уметь готовить, не забывать о подсказках ИДЕ и т.п. Но типичный программист, особенно типичный программист учившийся на каком-то опенкарте (или хуже того — писавший опенкарт) обязательно отстрелит себе ногу.
Думать в каком порядке ты указал родителей, в каком порядке тот кто будет наследоваться от тебя — будет указывать родителей… ну можно, но не всем доступно.
Писать в стиле «разряденные матрицы», т.е. так чтобы множественное наследование было только там где оба родителя не пересекаются по функционалу и именованию? Ну можно, даже нужно, но… никто не будет так делать. Если дали в руки пушку, то будем ею забивать гвозди.
Интерфейс это действительно абстрактный метод без реализации. И соответственно можно вполне писать интерфейсы из абстрактных классов. Только не будет никто так писать. (Вот здесь аргументация слабая, признаюсь, ибо тот кто не будет писать интерфейсы из абстрактных классов, не будет их и отдельно писать).
Пхп прощает очень многое, поэтому в нем очень много новичков, а значит для него как раз лучше сделать такой дизайн где все отдельно.
Благие намерения дизайнеров языков я не ставлю под сомнение! Но, кмк, проблема кроется здесь в отсутствии адекватных умолчаний: можно было бы ввести какие-то правила доминирования с возможностью их кастомизировать очумелыми ручками.
Буду рад, если это предложение предметно покритикуют.

Интерфейс это действительно абстрактный метод без реализации. И соответственно можно вполне писать интерфейсы из абстрактных классов. Только не будет никто так писать.
Тот, кто не будет так писать, все равно теперь собирает аналог абстрактного класса из интерфейсов с методами по умолчанию (=
UFO just landed and posted this here
  • При виртуальном наследовании отдавать приоритет веткам наследования от класса, который стоит раньше в списке наследования.
  • Различать виртуальное (по умолчанию) и невиртуальное наследование (допустим словом private, как в C++, но с иной семантикой).
  • Запретить использование нетривиальных конструкторов базового типа при виртуальном наследовании.
Интерфейс это действительно абстрактный метод без реализации. И соответственно можно вполне писать интерфейсы из абстрактных классов. Только не будет никто так писать. (Вот здесь аргументация слабая, признаюсь, ибо тот кто не будет писать интерфейсы из абстрактных классов, не будет их и отдельно писать).

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

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

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

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

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


class Entity {
  public Entity() {
    throw new Exception('Entity should not be instantiated'); 
  }
  public void save() {
    throw new Exception('method Entity.save() must be overridden in the ancestors')
  }
}
Можно было бы. В стандартной библиотеке Java полно подобных штук) *OperationNotSupportedException* Но плохо это по ряду очевидных причин. Как минимум, мы узнаем об этом лишь в рантайме.
В вашем примере «абстрактный» класс будет пытаться создавать клиент. Но написанием абстрактного интерфейса клиент заниматься точно не будет, поэтому поломать при желании он ничего не сможет. Это целиком зона ответственности разработчика API. А если уж тот захочет написать ерунду, то ему никто не в силах помешать.

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

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

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

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

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

Что-то вроде class A extends B implements C. Пока не залезем в B не узнаем нужно ли определять что-то или нет, наследуясь от него. А с C сразу ясно, что нужно определить всё. Интерфейс — это лишь свойство (в широком смысле слова) объекта семантически, протокол, который он поддерживает, как заметил michael_vostrikov, а наследование — его суть

Ну вы знаете, что интерфейс нужно имплементировать. Чем это вам поможет на практике? Вы не пойдете смотреть его методы?
Где-то в комментариях встречал хорошее определение. Интерфейс — это протокол взаимодействия. То есть задает распознаваемые сообщения, вход и выход. Зачем сюда пихать отношение иерархии?
А чем вам абстрактный класс — не протокол?
Тем, что задает отношение иерархии. Какая у него семантика?
Класс A является наследником классов X, Y, Z. Что это означает? Что из этого протокол, а что реализация?
Тем, что задает отношение иерархии.
Не делайте внутри общих данных и не будет. Абстрактность класса намекает на то, что он является неким контрактом, который клиенту необходимо поддерживать.

Что из этого протокол, а что реализация?
Мышку навести, абстрактное название намекает, добавить префикс, поместить в неймспейс «abstract», настроить IDE выделять курсивчиком и т.п.
Во-во. Введение правил использования сущности говорит о том, что сущность используется не по назначению.
Обо всех вот этих «не делайте», «добавить префикс». Если есть некие правила для обозначения чего-то, значит существующих средств языка недостаточно, и логично добавить новые, которые позволят явно это выражать.
Можете не делать, это не правила, а семантика, которой вы управляете)
Мне кажется, вы неправильно понимаете слово «семантика». Семантика это не то, то написано, а то, что означает написанное.
Вам кажется. Если вам нужен чисто абстрактный класс — вы не добавляете логику и данные, если они нужны — добавляете. Какой смысл выделять для одного случая отдельную синтаксическую конструкцию? Если я вижу метод API, в который нужно передать SomeType, то будет очевидно, что нужен свой тип-наследник и нет разницы, интерфейс это или абстрактный класс.
Разница в том, что в абстрактном классе может быть реализация, а в интерфейсе нет. Именно перекрытие реализации и приносит проблемы. А уж семантика «дочерний узел в иерархии» тут вообще непонятно зачем. Что она дает?

Смысл именно в семантике. Так же как и у наличия while/for/foreach, хотя все они сводятся к условным джампам.
Разница в том, что в абстрактном классе может быть реализация, а в интерфейсе нет.
Не факт. В языках появляются средства, чтобы внедрить реализацию в интерфейсы. Это тут обсуждалось.

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

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

А почему не эволюцией дизайна? Фикс предполагает ошибку, костыль — грубое решение конкретной проблемы, эволюция — полноценный ответ на новые требования. Были одни требования к языку — была одна версия. Появились новые требования (включая требование обратной совместимости) — язык эволюционировал и стал худо-бедно им отвечать. Если брать глобально историю развития PHP, то костылём в нём я могу назвать только две вещи глобально:


  • ООП в четвёртой версии
  • сильная контекстная зависимость операторов типа -> [] () при вычислении выражений, типа невозможности некоторое время написать (new ClassName)->method() или $a[7]->method()
А почему не эволюцией дизайна? Фикс предполагает ошибку, костыль — грубое решение конкретной проблемы
В предыдущем комментарии есть ответы.

Были одни требования к языку — была одна версия. Появились новые требования
Какие такие требования к языку поменялись? Программы всегда нужно было поддерживать, всегда хотелось, чтобы он был удобным и минималистичным. А «запилить фичу, чтоб как в языке X» — это не требования, а податливость трендам, которая иногда выходит боком.

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

появилось требование возможности удобной поддержки больших кодовых баз
Когда от языка не требовалось поддерживать большие кодовые базы?

Не «запилить фичу как в X», а «решить проблему P, так же как она решается в X, поскольку именно решение X выглядит оптимальным»
Разницу не уловил.
UFO just landed and posted this here

Когда на этом языке не планируется создавать большие кодовые базы. Косвенно об этом можно судить, например, по наличию слов "beginner's" или "personal" в названии. :)


"Запилить классы как в Java потому что можем, и вообще 21-век на дворе, а у нас ООПа нет" vs "Чтобы упростить поддержку больших кодовых баз, добавить возможности ООП, максимально близко к Java, поскольку именно в Java ООП выглядит оотимальным",

«Запилить классы как в Java потому что можем, и вообще 21-век на дворе, а у нас ООПа нет» vs «Чтобы упростить поддержку больших кодовых баз, добавить возможности ООП, максимально близко к Java, поскольку именно в Java ООП выглядит оотимальным»
Здесь все еще нет противоречия) Миграция идей туда-обратно по языкам — обычное дело в индустрии. Если хотите, то можно менее агрессивно: «чтобы приблизить его по удобству к X, а то вообще 21-век на дворе, а у нас ООПа нет».

В формулировках "Запилить классы как в Java потому что можем, и вообще 21-век на дворе, а у нас ООПа нет" или "запилить фичу как в X" нет указания на то, что язык станет в чем-то лучше после запиливания. Другие формулировки указывают на то, что хоть что-то да улучшится, что заимствование идей не самоцель или "ачивка" типа "у нас есть ООП", а решение каких-то практических проблем типа "сложно поддерживать большие кодовые базы" или "сильно проигрывает по удобству X".

нет указания на то, что язык станет в чем-то лучше после запиливания
Это не делает фразу противоречием)

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

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

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

Цель — объявлять отношения типа «является».
Да, пожалуй, я хотел сказать о том же, только отталкиваясь от низкоуровневых деталей типичной реализации.
Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.

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

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

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

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

class Base
{
    public void Foo() {}
}

interface IFoo
{
    void Foo();
}

class Derived: Base, IFoo
{    
}


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

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

Не будет. Реализация полиморфизма вообще не регулируется стандартом, обычно в классе пишется просто указатель на vtable и всё. Исключением может являться например ромбовидное наследование.
Пруф.
У вас пример кривой — вы вообще динамический полиморфизм не используете. Вот правильный: cpp.sh/93fd7

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

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

Да, ошибся. Но +8 байт на 1 подобный «интерфейс» — все равно много.
Почему наследование интерфейсом должно быть виртуальным, надеюсь, не нужно пояснять?

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

Вот только указатель на таблицу виртуальных методов — это тоже данные.

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

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

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

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

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

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


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

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

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

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

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

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

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

99% процентов джавистов не используют префикс «I» и живут же как-то!
Придумывая как составить прилагательное или обобщить существительное.
Просто не ставят префикс.
И ставят суффикс Impl у реализации, ага. (будто это лучше)
Неужто реализация настолько абстрактна, что выделить в имени типа ее особенности никак нельзя?
Кроме того, интерфейсы обычно лежат в отдельном пакете, поэтому конфликта имён не будет.

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


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

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

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

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


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

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

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

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

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

UFO just landed and posted this here

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


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

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

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

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


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


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

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


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

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

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


Ну, для С++ я вижу смысл — там нет такой языковой конструкции, как интерфейс. Отделить интерфейсы от классов через стайлгайд — нормальная идея.
Для языков, где интерфейсы есть — бесполезная буква.
тем не менее в C# принято ставить I.
Поддерживаю предыдущих докладчиков. Боб очень щепетильно относится к именованию, но с интерфейсами он малость перегнул. Правда, в том абзаце про имена интерфейсов он выражает мысль про нелюбовь к префиксам в такой форме, что становится ясно, что это его личное предпочтение, тем самым оставляя читателям право сделать собственный выбор.
Есть, несомненно, много неприкосновенных правил, которые нельзя нарушать в программировании, но использование префиксов для интерфейсов к ним не относится. Тут, я считаю, следует руководствоваться только личным удобством (и удобством коллег, конечно же).
Хм… сплошная критика… ради чего?) Пишите код как хотите…
Да, да, пишите как хотите, только адрес свой указать не забудьте. Бензопила у меня есть, справку купим…
Вот возьму и в сейфе дома дробовик заведу, от нерадивых которые то того чтоб молча
«молча курить» чужой код (как это делаю я) начинают кого-то винить — твой таск, твое дело со всеми вытекающими.
Обычно это проходит.
Достаточно поработать пару лет над проектом с парой дюжин разработчиков, когда в сфере твоей ответственности работает команда из пяти человек, а с остальными коммуникации редкие… Сразу понимаешь что дробовик купил не зря. Только с целью ошибся...)
Что, извините, проходит? Я всегда стою на позиции, твой таск — твое дело, и воспринимаю кодокопание в чертогах чужих мыслей как должное, ну не может быть такого, как хотят манагеры (когда об этом зашла речь — один мне сказал что к такому надо стремится) чтоб у всех мозги были выровнены по одним и тем-же лекалам. Да и вообще от одинаковости мозги закостенеют, а я такого не хочу.
Это 4 разных проблемы:

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

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


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

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

Ка по мне, то с популярностью js всё понятно.
— во фронтенде ему нет альтернативы
— условно низкий порог вхождения:
— — проще учиться с динамической типизацией
— — интерпретируемый язык, сразу видно результат
— — всё что необходимо, это браузер
— можно кодить как во фронтенде, так и в бэкенде
Особый толчок дало развитие фронтенда.
Всё это породило огромное сообщество, которое его активно развивает. Кроме того, его развитием занимаються большие корпорации, вроде Google, Facebook, Microsoft etc.
UFO just landed and posted this here

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

Senior — это «старший» по-русски.
«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».
Это выражение Джона Вудса, сказанное им в 1991 году на конференции.
С остальным в статье согласен — чем проще код, тем лучше.
Чисто на ментальном уровне мне нравиться эта мысль. Простой и понятный код — это произведение искусства. Иногда читаешь код, вроде понятно как это работает, а вроде それは明らかではない
У нас практика — не писать комменты, только документацию.
Почти не встречаются случаи — отковенная муть но я откомментировал и моя совесть чиста
Опасная фраза «тупой код». Тупые воспримут ее буквально и будут писать лапшеговнокод
От тупого читалателя защищаться — себе дороже…
Комментов больше чем слов в статье. Поддерживаю Mendel
пишите как хотите, только адрес свой указать не забудьте

Articles