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'))
Речь не о том, что лучше или хуже, но так же реально тупее, если применять это слово к коду, а не к его автору :)
Решает задачу наиболее простым образом
bool isBukva (char symbol)
{
return std::isalpha(symbol);
}
Вот только для русской кодировки такой однострочник не работал и пропускал букву Ё.
<C-r>=map(range(char2nr('a'), char2nr('z')) + range(char2nr('A'), char2nr('Z')), 'printf("case''%s'':return 1;break;", nr2char(v:val))')<CR>
Цитата №8.
#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 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;}}
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
Видите там наверху цикл? Он категорически некорректен (не делает экранирование правильно), но там вполне можно было написать 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!»)
А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость?
Читаемость улучшает, отлаживать сложнее (промежуточные значения не видно).
Но он платный. Надеюсь в будущих студиях или в решарпер добавят такую функциональность.
Я часто применяю тернарные операторы, вместо 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(); }
На вкус и цвет…
if (cond)
{
return true;
}
else
{
resturn false;
}
Раньше меня такие конструкции приводили в недоумение…
Теперь я начинаю понимать авторов, которые пишут:
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;
Я тоже ставлю скобки в таких выражениях. Без скобок при быстром чтении внутренний парсер сбивается. И визуально оно разделяется на 2 части по знакуvar result = (someValue == 0 ? true : false);
==
, а не по =
.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
???
Для не обрывающегося случая я всегда ставлю скобки, даже если там 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, иначе код не скомпилируется.
И это не относится к тем нюансам языка, которые могут запутать даже опытного разработчика.
Это самые базовые вещи.
Пытаться усложнять этот код, чтобы он был якобы более читаемым, это все равно что пытаться как-то усложнить запись вызова оператора умножения чисел ("а вдруг у нас гетерогенная разработка, и этот код прочитает программист Руби, а в Руби можно умножать строку на число — давайте напишем вызов оператора умножения, чтоб явно было видно, что мы перемножаем числа").
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 это очень распространено.Не всегда. Я, например, так пишу для того, чтобы при дебаге можно было поставить breakpoint на конкретную строчку на конкретное условие. Обычно, правда, в репозиторий это не идет, если ушло — значит, не заметил, поправлю на review.Так пишут не для простого кода и читаемости.if (cond) { return true; } else { return false; }
А пишут от непонимания даже базовых типов и операторов.
Иногда есть смысл в коде с таким ветвлением (хотя в случае дебага можно воспользоваться точкой останова с условием).
Бывает еще, когда ветвление более не только более читаемо, но и более масштабируемо — в том смысле, что может ожидаться, что блоки будут расширены дополнительным кодом.
Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строкуОт языка зависит. В плюсах — нет, не позволяет.
Пока в функции ровно одна строка — тернарник будет удобнее, как только он оказывается посреди тела функции хотя бы в 5 строчек размером — он резко теряется.
Изредка и только короткие тернарники действительно читаются легче, чем условия, но
PS всегда был приверженцем сокращённо-выделенного вида:
SomeType a;
if (condition) {
a = getVal1();
} else {
a = getVal2();
}
PPS
Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.
на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?
бы в 5 строчек размером — он резко теряется.Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.
на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?
F9 (мышкой, наверное, нельзя не уверен)
Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.
Ну вот порефакторили имя переменной и стало грустно читать. А кому-то и изначально было грустно читать. В общем как я написал — тернарник хорош в случаях когда он получается коротенький-маленький. В остальных случаях его использовать опасно/вредно для читабильности кода.
для меня такой вариант намного понятнее
ClosesType closes = isColdOutside ? COAT : TSHIRT
чем
ClosesType closes;
if (isColdOutside)
closes = COAT
else
closes = TSHIRT
Clothes
, вероятноОсмысленный выбор читаемость улучшает.
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;
Вот за использование производной группировки в исходной же переменной стоило бы попинать.
Да, так намного лучше.
Map<String,String>
для такого дела? (не знаю, на каком языке этот код написан; сравнение строк по значению накладывает ряд ограничений, но вариантов все-равно больше одного).Кроме того условие может быть немного нестандартным, например с регэкспом.
Ессно, в какой-то момент такую вещь феншуизируешь… Или нет.
Но, тем не менее, такой код читается. И часто лучше, чем map ;)
op =
&(
!y ? (!x ? upleft : x != last ? upper : upright ) :
y != bottom ? (!x ? left : x != last ? normal : right ) :
(!x ? lowleft : x != last ? lower : lowright )
) [w->orientation]
В ёлочку код не всегда читабельнее
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. Беспокоиться не стоит — это автосгенерённый код. Если выровнять — будет около полсотни вполне красивых строчек.
Почему-то мне казалось (из собственного опыта), что быстрый, эффективный и чистый, красивый код зачастую вещи противоположенные и приходится соблюдать баланс. Ну хоть бы взять пример выше — 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 сами по себе ссылки
// при этом считается, что ссылки не перекрываются во имя оптимизаций конечно же
Плюс там есть 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
вам уже указали и примеры привели. Честно говоря, мне кажется, всего этого должно быть достаточно, чтобы увидеть свою неправоту.
// 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
при этом ни при чем, потому что она не используется.
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 мутабелен — то нам-таки чёрная магия нужна. Что поменять местами две неизменяемые переменные-то!
Вы заблуждаетесь.Серьёзно? Давайте пример до конца допишем, а потом уж говорить будем.
Пример:А вы специально в примере используете не такие строки, как в Java? Давайте это исправим (реализация урезана, да, но идея, я думаю, понятна):
String foo = "foo"; String bar = "bar"; swap(foo, bar); // здесь foo приняло значение "bar", а bar" приняло значение "foo".
#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Потому что раз уж вы преврашаете 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&)
, скажем, делает следующее:- Создаёт временный обьект типа
int
. - Копирует значение первого аргумента в этот временный обьект.
- Копирует значение второго аргумента в первый.
- Копирует значение временного обьекта во второй аргумент.
в 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 все переменные передаются по значению. Только значение переменных, указывающих на объект это, как несложно догадаться, указатель :)
Что гораздо хуже — в языках типа 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...
В первом случае — параметры у нас таки передаются по ссылке, но агрументом является обьект
String
(как, собственно, в коде и написано), либо, альтернативно, что есть ещё и ссылки где-то (которых в коде, в общем-то, не видно, они для нас как тот «суслик») — тогда передача происходит по значению, но всё окончательно запутывается.Судя по тому, что вы написали выше ("Передача по ссылке в C++ даёт вам те же возможности, что и обычная передача для больгинства типов в Java."), у вас была содержательная, а не терминологическая ошибка в картине мира.
А что до терминологии, то она вполне стандартна, и "считать, что в Java всё и всегда передаётся по ссылке" нельзя.
В Java так сделать нельзя, что с int, что с Integer.В Java так сделать нельзя, не потому, что
Integer
передаётся по значению, а потому что он Immutable. Mutable
вариант — это, например int[]
с одним элементом.Не понял, что вы имеете в виду.Во всех ваших примерах на Java вы используете
immutable
типы. И изменить их нельзя не потому, что они «переданы по значению», а потому что они, в принципе, неизменяемы.В моём примере
String
— тоже неизменяемый и, внезапно, его «так просто» изменить нельзя — даже при передаче по ссылке.Изменяемость типа с этими вопросами не связана, см. другой мой коммент. К инстансу ArrayList
указанные аргументы применимы в равной степени.
std::swap
именно меняет ссылки, а не изменяет каждое из значений. В приведенном мной коде, кстати, int
. И это работает именно потому, что передача идет по ссылке, а не потому, что int
мутабелен.
И это работает именно потому, что передача идет по ссылке, а не потому, что int
мутабелен.
В вашей программе обьявить их иммутабельными нельзя, но можно её немного модифицировать — и всё. std::swap перестаёт работать.Ещё раз, для идиотов. Для работоспособности функций, подобных
std::swap
нужно выполнение не одного, двух условий:- Передача аргументов в функцию должна происходить не по значению, а по ссылке.
- Переданные значения не должны быть
immutable
(для них должен быть определёнoperator=
).
Только при выполнении обоих условий функция, подобная
std::swap
реализуема. Для примитивных типов в Java не выполняется первое условие, для типов Integer
и String
— второе, но возьмите ArrayList
— и всё получится!Ну хоть бы взять пример выше — LINQ позволяет сделать код куда компактнее, чище, проще для понимания, но ухудшает эффективность.
В чем LINQ ухудшает эффективность? Когда он плохо в sql смаплен?
Если говорить про Linq-to-Objects — многовато лишних аллокаций.
Во-первых, не всегда. Во-вторых — ну да, иногда теряется. Это далеко не всегда настолько важно.
В тех 7% где не растет — будет не тупой код, и комментарий «не трогайте тут, выигрываем несколько тактов»
Этот распространенный принцип очень нравится когда пишешь код, а вот когда используешь такие программы, то наоборот. Особенно, когда их надо несколько одновременно запускать.
На самом деле ровно наоборот.
Именно «умные» программы как правило и тормозят, поскольку по мере роста всю картинку не видит никто, и начинается нагромождение на нагромождение и так далее.
А простая структура, в которой каждый элемент прост и «понятен даже идиоту», там и доработать узкое место проще.
В сферическом проекте где время бесконечно — конечно лучше оптимизировать всё. А в реальном — всё что ты потратишь на преждевременную нанооптимизацию тебе придется откуда-то взять. И либо придется пожертвовать (выпустить много позже) фунционалом, либо не оптимизировать именно то что нужно.
Именно «умные» программы как правило и тормозят, поскольку по мере роста всю картинку не видит никто, и начинается нагромождение на нагромождение и так далее.Это решается абстракциями. Микрооптимизации на то и микро, что на общую архитектуру не влияют и живут в своих четко очерченных, хорошо закоментированных областях.
Если при написании программы, у вас спрашивали требования (нужно что бы быстро, нужно что бы несколько штук одновременно), то значит, нужно идти к авторам и тыкать пальцем «вот тут не соответствует».
Если же это массовый продукт, то видимо есть некий механизм, для обратной связи, и можно верить, что этот механизм рабочий.
Чудес, к сожалению, в этом месте не бывает. Вот, у меня в списке дежурных фраз есть
Тупые решения выигрывают у умных в 9 из 10 случаев. Будьте достаточно умны, что бы предлагать тупое решение
Практически вся эта статья в одной фразе.
Так-то да, а потом ты запускаешь Atom на ноутбуке старше 3х лет и начинаются боль и страдания.
По опыту — на производительность больше всего влияют архитектурные решения. И простой и очевидный код очень важен именно этим — очевидностью и простотой при рефакторинге. Проще расширить мост с "банальной" конструкцией, чем тот, в котором трудно рассмотреть на чем все, на самом деле, держится.
До середины 90-х нам иногда приходилось оптимизировать и коэффициент k в алгоритмах с линейной сложностью O(kN), но и тогда, когда компьютеры были бледнее "Малины", кроме того, что такие приемы были редки и очевидны — очень важно было следить за "внятностью речи".
И кошмар когда этого нельзя сделать.
Вроде смысл не сильно меняется, но акценты именно так стоят ибо как правило невозможность разнести в черные ящики это проблема архитектуры.
Тупой код обычно состоит из каких-то стандартных паттернов и стандартных приёмов. Таких приёмов, которые умеют находить и распознавать и оптимизировать компиляторы, интерпретаторы и JIT.
этот крутой чувак, из мира Java, кажется говорил не про метод использование if, switch, etc., а про метод реализации конкретных алгоритмом, реализация которых в чистом коде дает понять при чтении: это функция f, они принимает аргумент x и возвращает y, и в тупом коде ясно как день без лишних комментариев каким образом c x сделался y.
… другой вопрос — именование, не менее сложная задача чем писать простой код…
да и вообще, одна из любимых фраз: простые вещи ломаются реже сложных.
код, который может понять даже человек не знакомый с этим конкретным языком программированияНе к этому ли нужно стремиться?
код, который может понять даже человек не знакомый с этим конкретным языком программирования
Не к этому ли нужно стремиться?
Вряд ли к этому.
Допустим, мы пишем чистый и понятный код.
При этом к месту используем лямбды, 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#-разработчики?
del
Не путайте строгую типизацию с динамической. Для данного выражения нужно знать приоритет операций прежде всего, чтобы знать как оно будет понято транслятором. В языках со строгой типизацией транслятор выдаст ошибку, со слабой — куда-то чего-то приведёт. В языках с сильной статической типизацией ошибку выдаст в компайлтайме, с динамической в рантайме. По крайней мере я не припоминаю языков, в которых приоритет операций менялся бы от типа операндов. Типа, если в 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 раз
- Замыкание
Верно. Но для этого больше подходят локальные функции, только они есть не во всех языках.
То есть вместо, например, лямбды 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, чтобы, например, проверять перед попыткой поиска права пользователя на подтверждение договора. Причём основания так полагать мне дают уже имена классов в этом участке кода.
Я уже точно мало понимаю о чём мы :)
Я о том, что подобный код даёт мне основания предполагать следующее:
- Класс Contract реализует бизнес-логику какого-то договора, причём, скорее всего, достаточно инкапсулированную.
- Метод approve класса Contract осуществляет утверждение договора
- Класс ContractRepository реализует паттерн Repository для инстансов класса Contract, представляя постоянное хранилище данных (СУБД, внешний сервис, файлы, память) в виде коллекции. Причём на данном уровне мне абсолютно всё равно, какого вида хранилище, и как реализован класс. Он может быть реализован с помощью сторонней бибилиотеки, которая на порядок больше остального приложения, ещё большей самописной реализацией, а может несколько строк.
- ContractId скорее всего реализует паттерн ValueObject
- необходимости вызывать тут метод типа Contract.save нет, даже если он в классе имеется как унаследованный от базового класса какой-то библиотеки. Это при условии, что я знаю, что код рабочий.
Без знаний "кучи методов и паттернов, известных только в академической среде" мне пришлось бы лезть внутрь каждого класса, чтобы понять что к чему.
Хорошо, это я понял, да, на всех уровнях имеется магия при всей очевидной простоте кода. Однако подразумевается, что нужно использовать какой-либо фреймворк, или вываливать код тут же в одном файле, или как? Если готовый фреймворк — то что делать, когда нужен интерфейс взаимодействия с каким-либо банком, например, если не хочется писать свои велосипеды (может быть не проприетарно, плюс небезопасно в случае финансовых операций)? Любой фреймворк — это магия без документации, в которой тоже нужно копаться, если стек технологий для разраба новый. А если второе, то не нужно ли точно так же прокручивать весь код, дабы понять что делает тот или иной метод?
Все же мне до сих пор навязчиво кажется, что что-то все еще уходит от моего понимания.
Я не про магию, а про "академические паттерны". Даже если формально они усложняют код (всякие там цикломатические сложности), то по факту делают этот код проще для понимания разработчиком, эти паттерны знающие.
Простой, и основная простота в том, что там используется минимум три академических паттерна:
- Entity
- Repository
- ValueObject
и несколько принципов SOLID, в частности принцип единственной ответственности и принцип инверсии зависимостей
Код в котором нет никаких неожиданностей, ничего неочевидного, нет никаких крутых финтов, никаких исключений из правил, никаких нарушений архитектурного шаблонаДа и вообще, с этим нужно аккуратнее. Программист должен думать не о том, чтобы показать работодателю какой он крутой, а думать об оптимальности. Можно и гвозди кувалдой забивать, ибо из-за своего веса с одной стороны она более эффективна, только вот устанет человек быстрее чем забьет гвоздь. Плюс скорее всего раздербанит и поделку и сам гвоздь. А можно взять молоток. Он легкий и простой, и создан специально для этого. Другой вопрос что можно и им пальцы отбить, но это уже и вовсе зависит от подготовки джуниора, все-таки на работу не профанов обычно берут.
Сеньор отличается от новичка не качеством кода (новичок тоже может написать хороший код), а просто опытом. Сеньор много чего видел, чувствует какие проблемы могут всплыть с проектом и т. д.
Чтобы быть юниором нужно знать язык/платформу, быть знакомым с инструментами и уметь написать хелловорд.
Чтобы быть мидлом необходимо владеть основными принципами «простого» кода на уровне «могу выполнять», уметь соблюдать код-гайды и иметь уверенные навыки работы с инструментами. Для этого уже нужно немного опыта (в идеале с наставником, так быстрее).
Сеньору же да, нужно много опыта чтобы не просто уметь использовать парадигмы и инструменты но и четко понимать почему нужно именно так а не иначе (мидлу хватит и «потому что БОСС так сказал»).
Ну еще ЗП у них разные, если уж на то пошло. :D
В большинстве видов деятельности человека — уровень "сеньорности" определяется, в первую очередь, стабильностью определенного качества результата.
Другая их большая проблема, это совершенное непонимание, что будет с их кодом написанном в одном таске буквально на следующий день в их же следующем таске. Простейшее решение для них, это копипаста и последующее исправление всех (или нет) копипаст в случае изменений.
Simplicity is complicated
© Rob Pike
Далеко не всегда получается добиться простого кода и нормальной производительности без явных боттнеков.
В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом (кстати, тогда не стоит называть его тупым):
Неслучайно появился первый комментарий — конечно, код в нем утрирован (хотя… и подобное доводилось видеть в рабочих проектах).
Дело в том, что очень часто под таким кодом (чистым и понятным, в терминах статьи — тупым) понимается последовательная процедурная портянка, которую кто угодно прострочно прочитает.
Вряд ли такой можно назвать хорошим.
В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом
Это и в книгах порой не раскрывается, что уж говорить про статью.

Можете просто добавить ещё одну метрику WTF/стока кода и смотреть сразу по двум.
Между количеством строк и временем так же нет линейной закономерности, ведь простой код и читается легче. Даже если чтение (до полного понимания) занимает одинаковое время, то лучше это будет простой код, без неоднозначностей и прочих подводных граблей, о которых можно забыть.
Вряд ли количество кода для отдельного случая будет отличаться на два порядка) В 2 или 3 раза — это звучит уже намного менее драматично.Скорее в 20-30 раз. Многие «правильные приёмы» транслируют 2-3 строки в 40-50 строк легко. Заведение вместо одной простой функйии абстрактного класса, его потомка тому подобного (что внедрение зависимости может требовать), фабрики и прочее.
У меня было пара достаточно чистых экспериментов, когда мой «сложный код» переписывали и когда, наоборот, я переписывал тормозящий «тупой», но «гибкий» код. Разница была где-то между 10x и 100x, а не в «в 2 или 3 раза — это звучит уже намного менее драматично».
Даже если чтение (до полного понимания) занимает одинаковое время, то лучше это будет простой код, без неоднозначностей и прочих подводных граблей, о которых можно забыть.Это — тоже фикция. «Простой» и «гибкий» код я перписывал именно потому, что добавление новых фич занимало слишком много времени. Так как там было порядка 100'000 строк кода, то «до полного понимания» дело не доходило — в нём просто было слишком много всего, чтобы можно было его полностью понять и все ньюансы запомнить. У его замены примерно на 5'000 кода была другая проблема — без полного понимания того, как разные части взаимодействуют между собой там было невозможно поправить фактически ничего. Но натурный эксперимент показал, что за неделю человек в этом разбирается и может менять то, что нужно менять добавляя 2-3 строки тут и там.
Такие дела.
Впрочем есть одно но: если размер компонента таков, что его при любом написании нельзя будет полностью уместить в одной голове — тогда, наверное, «тупой код» лучше.
«Простой» и «гибкий» код я перписывал именно потому, что добавление новых фич занимало слишком много времени
в нём просто было слишком много всего, чтобы можно было его полностью понять и все ньюансы запомнитьОксюморон. Тогда это было что угодно, но не гибкий код, очевидно.
Хороший код позволяет оперировать абстракциями, без необходимости полностью понимать и запоминать все нюансы реализации.
А как хотелось бы чтоб они вообще не появлялись.
-Почему у тебя код такой не гибкий — вдруг понадобится Х?
Через три дня к другой задачи
-Почему у тебя в коде абстрации?
-вдруг понадобится Х
-А МЫ НЕ ПИШЕМ КОД ПО ПРИНЦИПУ: ВДРУГ ВОЙНА, А Я УСТАВШИЙ, УБЕРИ РАЗ БЕЗ НИХ СЕЙЧАС МОЖНО ОБОЙТИСЬ.
Как-то скопипастил код в проект со stackoverflow, а лид говорит:
зачем скопипастил? Я же вижу, не твой почерк.
В оригинале было «Dumb Code». Причём, было не у автора, а у Гетца. «Простой», на мой взгляд, здесь не подходит по смыслу. Возможно, было бы лучше «код для тупых» или «код как для тупых» — что-то в этом духе.
И ещё не подходит потому, что «Write Dumb Code» — это, по сути, фраза-провокация, вброс. И сам блог-пост Скотта — это тоже по большей части провокация. В нём нет ничего нового, и никакой темы в нём не раскрывается. Всю суть текста можно свести к одной фразе:
«Код — это общение между людьми и инструкции для компьютера, но значительно больше первое, чем второе»,
которая, фактически, является перефразированной цитатой Кента Бека. Цель поста — в очередной раз привлечь внимание к злободневной проблеме. И попытка удалась, что тут скажешь. Скотт — красафчег!
А если нужна информация по теме простого и понятного кода, то есть хорошие книжки. Только они длинные.
А зачем ставить букву I? Я в курсе про венгерскую нотацию, если что. Но когда при чтении кода нам жизненно важно знать, что это интерфейс, а не класс (может быть абстрактный без одного конкретного метода и свойства)?
Так я не пойму, зачем их при чтении выделять вообще. Ладно при написании, заметить что написал new ICountable на секунду раньше, чем об этом сообщит IDE. Но читающему-то какая разница?
Не очень понял, что имеется в виду. То, что читателю намекается на то, что он свою иерархию построить не сможет в языках без множественного наследия? Как часто это бывает полезно? Особенно учитывая наличие принципа инверсии зависимости.
Конкретно это решение было не из самых удачных: в C# и Java появляются костыли для обхода этого органичения — внедрение реализации в интерфейсы теми или иными путями.
Я то пишу. Но я не один во Вселенной, и есть куча народаНичего против этого не имею. Но ограничение на наследование от этого менее унылым не становится.
А в 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.
Не чтобы исправлять, а сделать более удобным в использовании, прежде всего чтобы меньше писать одинакового кода в классах, реализующих тот или иной интерфейс. Множественное наследование решило бы эту проблему тоже, но оно порождает другие проблемы типа сложных алгоритмов разрешения при ромбовидном наследовании.
Дизайнеры языка редкую сложную проблему сделали насущной (ох, как же бестолковые интервьюеры любят мучить новичков-кандидатов этой разницей интерфейсов и абстрактных классов), а элегантную замену функциональности так и не придумали: в C# — внезапно в пространстве имен появляется какой-то сторонний класс, который докидывает функциональности другой сущности. В PHP было
extends
и implements
, а теперь еще и какое-то use
, с похожей, но различной семантикой. Это ли красиво и понятно?Семантика у них вполне прозрачная и соответствует разным ситуациям:
- implements — добавляет только объявление методов, обязывая этим класс сделать свою реализацию, с возможностью передавать реализацию туда где затребован интерфейс
- extends — добавляет существующую реализацию из родительского класса, с возможностью передавать дочернюю реализацию туда где затребован интерфейс родителя
- use — добавляет существующую реализацию, без возможности ее требовать и передавать, практически аналог include.
Здесь можно рассуждать сколько угодно, что это был изначальный план, но объективный факт: эти вот миксины, методы расширения и дефолтные реализации методов в интерфейсах — это результат эволюции языков, их не было изначально, но они понадобились. Поэтому я и называю это фиксом дизайна или даже костылями, акцентируясь на том, что они усложняют синтаксис.
И вы не с той стороны смотрите. Цель не вынести общий код из одного места в другое, а удобное использование этого кода в третьем, включая контроль типов. Если вы не указываете типы переменных, полагаясь на возможность языка вызвать любой метод по названию, то да, особой разницы нет.
Третий нужен, чтобы достигать цель без привлечения проблем второго.Так и я о том же и говорю) Только вы видите в этом отдельную подзадачу для которой прям необходима отдельная синтаксическая конструкция, а я вижу частный случай привычного решения.
При множественном наследовании есть проблема «кто чей родитель». Да, спокойно можно сделать решение в духе «кто первый тот и папа», и это прекрасно работает. Если это помнить, знать, и уметь готовить, не забывать о подсказках ИДЕ и т.п. Но типичный программист, особенно типичный программист учившийся на каком-то опенкарте (или хуже того — писавший опенкарт) обязательно отстрелит себе ногу.
Думать в каком порядке ты указал родителей, в каком порядке тот кто будет наследоваться от тебя — будет указывать родителей… ну можно, но не всем доступно.
Писать в стиле «разряденные матрицы», т.е. так чтобы множественное наследование было только там где оба родителя не пересекаются по функционалу и именованию? Ну можно, даже нужно, но… никто не будет так делать. Если дали в руки пушку, то будем ею забивать гвозди.
Интерфейс это действительно абстрактный метод без реализации. И соответственно можно вполне писать интерфейсы из абстрактных классов. Только не будет никто так писать. (Вот здесь аргументация слабая, признаюсь, ибо тот кто не будет писать интерфейсы из абстрактных классов, не будет их и отдельно писать).
Пхп прощает очень многое, поэтому в нем очень много новичков, а значит для него как раз лучше сделать такой дизайн где все отдельно.
Буду рад, если это предложение предметно покритикуют.
Интерфейс это действительно абстрактный метод без реализации. И соответственно можно вполне писать интерфейсы из абстрактных классов. Только не будет никто так писать.Тот, кто не будет так писать, все равно теперь собирает аналог абстрактного класса из интерфейсов с методами по умолчанию (=
- При виртуальном наследовании отдавать приоритет веткам наследования от класса, который стоит раньше в списке наследования.
- Различать виртуальное (по умолчанию) и невиртуальное наследование (допустим словом
private
, как в C++, но с иной семантикой). - Запретить использование нетривиальных конструкторов базового типа при виртуальном наследовании.
Почему опытные разработчики пишут тупой код и как распознать новичка за километр