Одна из моих любимых цитат на все времена — Брайана Гетца (Brian Goetz), умнейшего чувака из мира Java, одного из авторов «Java Concurrency in Practice», кроме всего прочего. Цитата взята из интервью, опубликованном на сайте Oracle под заголовком «Пишите тупой код» («Write Dumb Code»). Гетца спросили, как писать хорошо работающий код.
Вот что он ответил:
«Часто писать быстрый код на Java означает писать тупой код — код простой, чистый и следующий самым очевидным объектно-ориентированным принципам».
В остальных 1000 слов объяснялось, почему попытки оптимизировать код и попытки быть умными — это распространённые программерские ошибки, ошибки новичков, если хотите.
Код опытных разработчиков
Если вы, подобно мне, были когда-то джуниором, возможно, помните, как впервые исследовали код сениора и думали: «Я могу так писать. Почему я не сениор?».
Я длительное время пытался писать такой код, и у меня не получалось.
Загадочным в коде сениора было не то, что я не мог его понять, а что я мог его понять моментально. Он был конкретно тупым и казалось, что там должно быть что-то ещё. Я думал: «Где остальное? Как этот код делает всё это?»
С тех пор я изучил названия всех принципов и признаков кода, делающих его тупым: YAGNI, принцип единственной ответственности, DRY, принцип единственного уровня абстракции, слабое зацепление и т. д. И я стал сениором. На самом деле я ненавижу термин «сениор» и называю себя просто разработчиком ПО, но это другая история.
Главный урок, который я извлёк: писать тупой код трудно, но это приносит экспоненциальную выгоду.
Как отличить джуниора за километр
В книге «Refactoring: Improving the Design of Existing Code» Кент Бек говорит:
«Любой дурак может писать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям.»
Вы всегда сможете узнать начинающего разработчика, просматривая код полный «умных» однострочников, сомнительных абстракций и массы специфических языковых конструкций. Я бы сказал, что последнее наиболее характерно. Это выглядит, как-будто код пытается сказать: «Посмотри на меня! Мой создатель отлично знает язык! Я использую локальный потоковый синхронный конструктор копирования JavaBean с интерфейсом по-умолчанию и непроверяемыми исключениями кастомных дженериков вместе с многофункциональным генератором кода JAXB Lombok с усиленной безопасностью!».
Да, я сказал чепуху, потому что в чепуху превращается код в руках тех, кто думает исключительно о компьютерной стороне вещей вместо человеческой.
Код — это общение между людьми и инструкции для компьютера, но значительно больше первое, чем второе. Компилятор сам заботится о преобразовании написанного программистом в машинный код. Часто имеет место несколько слоёв такого преобразования, например, когда Java компилируется в байт-код, который считывается виртуальной машиной и транслируется в итоге в нули и единицы.
Но код — это человеческий язык. Он объясняет все «кто», «что», «когда», «где», «как» и «почему» задачи, заодно давая инструкции компьютеру. Он должен иметь смысл и пять лет спустя, когда компания будет продана, и новая команда, никогда не видевшая этого кода, откроет его для улучшения и исправления.
Да, написание тупого кода — это трудно. Я убеждаюсь в этом всё больше и больше с течением времени. Я чувствую удовлетворение, когда получаю коменты типа: «Чистый код!» на код-ревью. Я знаю, что лучшая вещь, которую я могу сделать для своей команды и для будущих мейнтейнеров кода — писать тупой код.
Вот мысль, которую приписывают Dave Carhart:
«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».
Комментарии (312)
michaelkl
21.01.2018 22:16SergeyGalanin Автор
22.01.2018 08:36Да, видел такую версию, когда делал перевод. Тут я склонен предположить, что уже в те времена это был известный в узких кругах мем, а Джон просто его процитировал.
В оригинале же упоминался именно Дейв, и ему тоже эту цитату приписывают, так что решил оставить его.
dmitry_dvm
21.01.2018 22:42Я часто применяю тернарные операторы, вместо if else, это тупой код? А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость? По-моему нет, но это и понятно, иначе я бы так не писал.
lair
21.01.2018 22:55А еще последнее время стараюсь все связанное со списками делать через LINQ, в т.ч. foreach. Это ухудшает читаемость?
Читаемость улучшает, отлаживать сложнее (промежуточные значения не видно).
aidarchikable
22.01.2018 16:43Есть хороший дебаггер для LINQ www.oz-code.com
Но он платный. Надеюсь в будущих студиях или в решарпер добавят такую функциональность.
webkumo
22.01.2018 03:00Я часто применяю тернарные операторы, вместо if else, это тупой код?
Нет, это машиночитаемый код. Человеку проще if-else прочитать как бы… Да и в дебаге сильно удобнее, да.
ad1Dima
22.01.2018 06:03Всегда ли проще читать?
var a = cond ? GetVal1() : getVal2();
SomeType a = null; if(cond) { a = GetVal1(); } else { a = getVal2(); }
Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.Oxoron
22.01.2018 14:24// Variant 1 var a = cond ? GetVal1() : GetVal2(); // Variant 2 SomeType a = null; if(cond) { a = GetVal1(); } else { a = getVal2(); } // Variant 3 SomeType a = null; if(cond) { a = GetVal1(); } else { a = getVal2(); }
На вкус и цвет…sumanai
22.01.2018 16:01На PHP можно даже без скобок
// Variant 4 $a = null; if($cond) $a = getVal1(); else $a = getVal2();
Но это не значит, что нужно так писать. Обычно такое должно регулироваться стайл гайдами.
plMex
22.01.2018 16:43-1Теперь я начинаю понимать авторов, которые пишут:
if (cond) { return true; } else { resturn false; }
Раньше меня такие конструкции приводили в недоумение…boblenin
22.01.2018 18:35Им платят за строчки кода. Если бы они просто назвали `cond` читаемым именем не надо было бы городить костыли.
sand14
22.01.2018 21:51Теперь я начинаю понимать авторов, которые пишут:
if (cond) { return true; } else { resturn false; }
Так пишут не для простого кода и читаемости.
А пишут от непонимания даже базовых типов и операторов.
Еще часто встречается такой код:
return cond ? false : true;
var result = cond ? false : true;
Или такой:
var result = someValue == 0 ? true : false;
И высший пилотаж (скобки на всякий случай для уверенности):
var result = (someValue == 0 ? true : false);
Что характерно, эти портянки как раз и объяснялись необходимостью "простого кода, понятного любому разработчику, не знающего тонкости языка".
Вместо того, чтобы написать:
return cond;
return someValue == 0;
michael_vostrikov
23.01.2018 00:00
Я тоже ставлю скобки в таких выражениях. Без скобок при быстром чтении внутренний парсер сбивается. И визуально оно разделяется на 2 части по знакуvar result = (someValue == 0 ? true : false);
==
, а не по=
.
someValue = a + b; result = someValue == 0 ? result1 : result2; // someValue равно a плюс b // result равно someValue, ой то есть bool, ой тут тернарный оператор, еще раз, result равно (someValue равняется нулю | да ... нет ...)
sand14
23.01.2018 10:19А почему бы тогда так не писать для пущей надежности?:
var result = ((someValue == 0) ? true : false);
Впрочем, если мы возвратимся к сути примера, то все гораздо проще:
var result = someValue == 0;
Ну или ок, пусть так:
var result = (someValue == 0);
В любом случае, все ясно и кратко, и можно со скобками, можно без.
А то ведь можно дойти и до такого (почему нет)?:
var result = ((someValue == 0) ? (true ? true: false ) : (false ? false : true));
michael_vostrikov
23.01.2018 12:02А почему бы тогда так не писать для пущей надежности?
Тут необязательно, так как в тернарном операторе первое условие всегда логическое. Хотя если условие будет длинное, то лучше поставить.
А то ведь можно дойти и до такого (почему нет)?
Потому что не повышает читаемость.
В любом случае, все ясно и кратко, и можно со скобками, можно без.
Согласен. С другой стороны, это отвязка от конкретно булевского типа, так проще рефакторить. Например, мы в новом коде сделали true/false, но не уверены, возможно потом будет лучше сделать 0/1/-1. Так мы можем просто заменить значения в ветках на любой другой тип. Но это редко когда нужно.
ad1Dima
23.01.2018 06:01Ну, у меня например не return, так что…
Для не обрывающегося случая я всегда ставлю скобки, даже если там 1 оператор. Потому это однозначно, и не зависит от отступов. Был нет так давно весёлый баг в популярном опенсорсе:
if (cond)
SomeFunc();
+ SomeOtherFuncThatMusBeInIFBlock();
OtherMethods();
VolCh
23.01.2018 10:16return cond ? false : true;
иreturn cond;
в целом неравнозначны :) А если имелось в видуreturn cond ? true : false;
, то в некоторых языках и они не равнозначны, если cond не строго булевого типа, а лишь в некоторых случаях типаif (func())
неявно приводится к нему. Даже если типа bool вообще нет, как в C (или уже есть?) и где-то в языке заложено#define true 1
, тоreturn cond;
в случае когдаcond
может отказаться равно, например, 2, может приводить к сложно диагностируемым ошибкам, если клиентfunc()
ожидает строго 1 или 0 в возврате.sand14
23.01.2018 10:29Это опечатка, имелось в виду, конечно, return cond? true: false.
Если результат нужно инвертировать, то тернарный оператор также не нужен, достаточно написать return !cond.
Это я написал про C# и код на нем, который часто доводится видеть.
Что касается других языков, того же C, то там нужно смотреть какие конкретно нужны значения, и их и возвращать.
(Обычно там всегда 0 для false, а для true есть варианты, обычно используются разные подходы в зависимости от 8/16/32-разрядности, либо 1, либо "не ноль", либо минус 1.)
И бездумность применения операторов, в отличие от C#, кроме некрасивого и избыточного кода, принесет и реальные ошибки.ZyXI
23.01.2018 10:38Если можно получить
bool
унарным!
, то не логичнее ли просто два раза инвертировать, если инвертировать не нужно, аbool
нужен?sand14
23.01.2018 10:47Верно, я про это и пишу. Когда видишь код:
return cond? true: false;
то хочется предложить еще варианты:
return !!cond;
VolCh
23.01.2018 11:13А вот такой вариант приведения к bool уже воспринимается менее однозначно чем
cond ? true : false
в общем случае, вплоть до допускания возможности, что транслятор просто проигнорирует двойное отрицание в целях оптимизации. Тут уже нужно в доки языка лезть.sand14
23.01.2018 11:28+1Так в том то и дело, что C#, о котором изначально шла речь, cond? true: false не выполняет приведения к bool.
cond — уже(!) bool.
Тернарный оператор в C# предназначен для возвращения одного из двух значений какого-либо другого типа, отличного от bool (а приведение он не умеет делать — попробуйте поиграться хотя бы с Nulllable/NotNullable/null).
Если он возвращает bool — это избыточный код, который как минимум замусоривает код, и хорошо еще, если компилятор это убирает.
И да, доки читать нужно. Т.к. в любом языке все операторы и ключевые слова выглядят примерно одинаково (да и возможный набор моделей более-менее одинаков — процедурная/ОО/ФП), а значить могут весьма разное — если смотреть как именно это работает, а не по верхам.
VolCh
23.01.2018 12:51Ну, если cond гарантированно является bool и, желательно, это видно на одном экране с тернарником, то, да, излишний код cond? true: false, но !!cond вообще сбивает с толку в таком случае, наталкивает на мысли об ошибке, что имелось в виду одинарное отрицание.
Про доки в целом согласен, но при ревью подобного не библиотечного кода лично я бы учитывал насколько в конкретной компании распространена, как иногда говорится, гетерогенность разработки. В частности, если заведомо известно, что много разработчиков, часто переключающихся между языками, то я бы старался не допускать код, связанный с нюансами конкретного языка.
sand14
23.01.2018 13:45В C#, Java (и, предполагаю, в любом современном статически типизированном языке, кроме C) в выражении cond? A: B первый операнд (cond) всегда является bool, иначе код не скомпилируется.
И это не относится к тем нюансам языка, которые могут запутать даже опытного разработчика.
Это самые базовые вещи.
Пытаться усложнять этот код, чтобы он был якобы более читаемым, это все равно что пытаться как-то усложнить запись вызова оператора умножения чисел ("а вдруг у нас гетерогенная разработка, и этот код прочитает программист Руби, а в Руби можно умножать строку на число — давайте напишем вызов оператора умножения, чтоб явно было видно, что мы перемножаем числа").
alexeykuzmin0
23.01.2018 14:13В C#, Java (и, предполагаю, в любом современном статически типизированном языке, кроме C)
Потому что в C# и Java более строгая типизация, чем в С. Это о современности особо не говорит.
ZyXI
23.01.2018 10:34bool
в C уже давно есть, и приведение к нему, явное или неявное, может выдать толькоtrue
илиfalse
. Хотя вы всё ещё можете получить сложнодиагностируемую ошибку, если будете заполнятьbool *
какими?нибудь двойками черезmemset()
или любым другим из кучи обходных путей — минимально адресуемая единица памяти до сих пор байт и одинbool
(если он не битовое поле в структуре) будет занимать именно его.sand14
23.01.2018 10:52Хотя вы всё ещё можете получить сложнодиагностируемую ошибку, если будете заполнять bool * какими?нибудь двойками через memset() или любым другим из кучи обходных путей — минимально адресуемая единица памяти до сих пор байт и один bool (если он не битовое поле в структуре) будет занимать именно его.
Даже в C# такое возможно — заполнять bool произвольным однобайтным значением, пометив код как unsafe, либо использовать структуры и FieldOffset из средств маршаллинга данных в/из неуправляемого кода.
В последнем случае не потребуется помечать код как unsafe и, соответственно, код не потребует привилегий при исполнения.
А поведение при работе с такой булевой переменной будет с ошибками, и еще будет зависеть от версии компилятора.
alexeykuzmin0
23.01.2018 14:07+1Даже если типа bool вообще нет, как в C (или уже есть?) и где-то в языке заложено #define true 1, то return cond; в случае когда cond может отказаться равно, например, 2, может приводить к сложно диагностируемым ошибкам
Можно использовать код
В boost это очень распространено.return !!cond;
alexeykuzmin0
23.01.2018 13:55
Не всегда. Я, например, так пишу для того, чтобы при дебаге можно было поставить breakpoint на конкретную строчку на конкретное условие. Обычно, правда, в репозиторий это не идет, если ушло — значит, не заметил, поправлю на review.
Так пишут не для простого кода и читаемости.if (cond) { return true; } else { return false; }
А пишут от непонимания даже базовых типов и операторов.sand14
23.01.2018 14:32Естественно, должно быть без фанатизма в плане краткости кода.
Иногда есть смысл в коде с таким ветвлением (хотя в случае дебага можно воспользоваться точкой останова с условием).
Бывает еще, когда ветвление более не только более читаемо, но и более масштабируемо — в том смысле, что может ожидаться, что блоки будут расширены дополнительным кодом.alexeykuzmin0
23.01.2018 18:21в случае дебага можно воспользоваться точкой останова с условием
В VC++, к сожалению, скорость дебага от этого падает в несколько раз.
reddot
22.01.2018 16:49-1Строго говоря — этот код не идентичен. В случае тернарного оператора в любом случае будут вычислены оба выражения.
Chaos_Optima
22.01.2018 16:55+1С чего это вдруг? Также будет вычисляться лишь одно. ideone.com/hy39CZ
ad1Dima
22.01.2018 18:51В свифте?
В C# — нет.
docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/operators/conditional-operator
alexeykuzmin0
22.01.2018 18:09Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку
От языка зависит. В плюсах — нет, не позволяет.
webkumo
22.01.2018 20:35Пока в функции ровно одна строка — тернарник будет удобнее, как только он оказывается посреди тела функции хотя бы в 5 строчек размером — он резко теряется.
Изредка и только короткие тернарники действительно читаются легче, чем условия, но
PS всегда был приверженцем сокращённо-выделенного вида:
SomeType a; if (condition) { a = getVal1(); } else { a = getVal2(); }
PPS
Ну и студия позволяет ставить брейкпойнты в ветках тренарного оператора записанного в одну строку.
на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?
ad1Dima
23.01.2018 06:05бы в 5 строчек размером — он резко теряется.
Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.
на строку брекпоинт можно поставить легко быстро и удобно, с поблочными всё же наверное побольше телодвижений надо сделать?
F9 (мышкой, наверное, нельзя не уверен)webkumo
23.01.2018 12:01Значит форматирование функции хромает. Так или иначе, мой коммент был про то, что if не всегда читаемее, чем ?:. А испоганить читаемость тренарного оператора можно просто длинной любого из трех его выражений.
Ну вот порефакторили имя переменной и стало грустно читать. А кому-то и изначально было грустно читать. В общем как я написал — тернарник хорош в случаях когда он получается коротенький-маленький. В остальных случаях его использовать опасно/вредно для читабильности кода.
esef
22.01.2018 16:43все дело в привычке. Для простых условий, как по мне, намного проще прочитать однострочный тенарный оператор чем продиратся через четыре строчки if-else
для меня такой вариант намного понятнее
ClosesType closes = isColdOutside ? COAT : TSHIRT
чем
ClosesType closes; if (isColdOutside) closes = COAT else closes = TSHIRT
ad1Dima
23.01.2018 06:06Мне не жалко конечно, но habrahabr.ru/post/347166/?reply_to=10629362#comment_10627954
areht
22.01.2018 06:59Там семантика (обычно) разная: LINQ — query, foreach — command.
Осмысленный выбор читаемость улучшает.
andreysmind
22.01.2018 10:44Работал много лет назад с одним парнем. Он писал вот так:
subCategoryName = subCategoryName == 'Pets and animals' ? 'Animals' : subCategoryName == 'Food and wine' ? 'Food/wine' : subCategoryName == 'Opinions and philosophy' ? 'Opinions' : subCategoryName == 'Health and wellness' ? 'Wellness' : subCategoryName == 'Design and architecture' ? 'Design' : subCategoryName;
mclander
22.01.2018 14:43О! Так это же я был))) Не то чтобы часто, но использую конструкцию.
Кстати, если разбить по строкам читается на удивление хорошо. Прув:
subCategoryName = subCategoryName == 'Pets and animals' ? 'Animals' : subCategoryName == 'Food and wine' ? 'Food/wine' : subCategoryName == 'Opinions and philosophy' ? 'Opinions' : subCategoryName == 'Health and wellness' ? 'Wellness' : subCategoryName == 'Design and architecture' ? 'Design' : subCategoryName;
Вот за использование производной группировки в исходной же переменной стоило бы попинать.Ogra
22.01.2018 14:57Не заметить в этой простыне = вместо == и искать потом баги — бесценный опыт. Так и становятся сеньорами =)
boblenin
22.01.2018 18:39Ага у нас один индиец таким вот кодом биллинг запортачил. Перепутал == и =. Месяц одним клиентам приходили лишние счета, а другие получали сервис бесплатно. Сеньором он становится будет в другом месте.
andreysmind
22.01.2018 15:03+1Он именно в строку писал. Этот код у меня хранится специально для аргументов про тернарные операторы. :)
Да, так намного лучше.mclander
24.01.2018 18:10+1За написание тернарников в одну строку и без скобок надо сразу лишать печенек.
Тернарная запись, если не читается однозначно — это источник батхерта, особенно при отладке. Не говоря о отладке чужих багов;)
mobi
22.01.2018 15:31А почему бы не завести какой-нибудь
Map<String,String>
для такого дела? (не знаю, на каком языке этот код написан; сравнение строк по значению накладывает ряд ограничений, но вариантов все-равно больше одного).mclander
24.01.2018 18:07Ну обычно такая конструкция заводится неожиданно. Сперва одно условие, потом два и понеслась. Проще дописать одно условие, чем феншуизироваться с риском ошибки.
Кроме того условие может быть немного нестандартным, например с регэкспом.
Ессно, в какой-то момент такую вещь феншуизируешь… Или нет.
Но, тем не менее, такой код читается. И часто лучше, чем map ;)
Gryphon88
22.01.2018 15:01можно и так, если грамотно форматировать. Пример из книги «Анализ программного кода на примере проектов Open Source», стр 69
op = &( !y ? (!x ? upleft : x != last ? upper : upright ) : y != bottom ? (!x ? left : x != last ? normal : right ) : (!x ? lowleft : x != last ? lower : lowright ) ) [w->orientation]
В ёлочку код не всегда читабельнее
me21
22.01.2018 16:44Ну, если после двоеточия переносить проверку следующего условия на новую строку под предыдущей проверкой, то тоже ничего читается.
DistortNeo
22.01.2018 17:23Вот нашёл такой кусочек кода (строка длиной 2834 символа) в проекте:
bool specChanged = cur.SpecificNodesParameters.Nodes != prev.SpecificNodesParameters.Nodes || (cur.SpecificNodesParameters.Nodes && (cur.SpecificNodesParameters.Objects != prev.SpecificNodesParameters.Objects || (cur.SpecificNodesParameters.Objects && (cur.SpecificNodesParameters.Attributes != prev.SpecificNodesParameters.Attributes || (cur.SpecificNodesParameters.Attributes && (cur.SpecificNodesParameters.AttributeData != prev.SpecificNodesParameters.AttributeData || cur.SpecificNodesParameters.AttributeInfo != prev.SpecificNodesParameters.AttributeInfo)) || cur.SpecificNodesParameters.Measurements != prev.SpecificNodesParameters.Measurements || (cur.SpecificNodesParameters.Measurements && (cur.SpecificNodesParameters.MeasurementData != prev.SpecificNodesParameters.MeasurementData || cur.SpecificNodesParameters.MeasurementInfo != prev.SpecificNodesParameters.MeasurementInfo)) || cur.SpecificNodesParameters.OperativeMeasurements != prev.SpecificNodesParameters.OperativeMeasurements || (cur.SpecificNodesParameters.OperativeMeasurements && (cur.SpecificNodesParameters.OperativeMeasurementData != prev.SpecificNodesParameters.OperativeMeasurementData || cur.SpecificNodesParameters.OperativeMeasurementInfo != prev.SpecificNodesParameters.OperativeMeasurementInfo)) || cur.SpecificNodesParameters.MonitoringValues != prev.SpecificNodesParameters.MonitoringValues || (cur.SpecificNodesParameters.MonitoringValues && (cur.SpecificNodesParameters.MonitoringValueData != prev.SpecificNodesParameters.MonitoringValueData)) || cur.SpecificNodesParameters.UserValues != prev.SpecificNodesParameters.UserValues || cur.SpecificNodesParameters.Events != prev.SpecificNodesParameters.Events || (cur.SpecificNodesParameters.Events && (cur.SpecificNodesParameters.EventData != prev.SpecificNodesParameters.EventData || cur.SpecificNodesParameters.EventInfo != prev.SpecificNodesParameters.EventInfo)) || cur.SpecificNodesParameters.ObjectInfo != prev.SpecificNodesParameters.ObjectInfo)) || cur.SpecificNodesParameters.Query != prev.SpecificNodesParameters.Query || (cur.SpecificNodesParameters.Query && (cur.SpecificNodesParameters.Response != prev.SpecificNodesParameters.Response || (cur.SpecificNodesParameters.Response && (cur.SpecificNodesParameters.NodeQueryResponseData != prev.SpecificNodesParameters.NodeQueryResponseData)) || cur.SpecificNodesParameters.NodeQueryStatusData != prev.SpecificNodesParameters.NodeQueryStatusData)) || cur.SpecificNodesParameters.NodeConfigData != prev.SpecificNodesParameters.NodeConfigData || cur.SpecificNodesParameters.NodeConnectionData != prev.SpecificNodesParameters.NodeConnectionData || cur.SpecificNodesParameters.NodeStatusData != prev.SpecificNodesParameters.NodeStatusData || cur.SpecificNodesParameters.NodeInfo != prev.SpecificNodesParameters.NodeInfo));
P.S. Беспокоиться не стоит — это автосгенерённый код. Если выровнять — будет около полсотни вполне красивых строчек.
Alex_ME
21.01.2018 22:55Почему-то мне казалось (из собственного опыта), что быстрый, эффективный и чистый, красивый код зачастую вещи противоположенные и приходится соблюдать баланс. Ну хоть бы взять пример выше — LINQ позволяет сделать код куда компактнее, чище, проще для понимания, но ухудшает эффективность. Или иммутабельность, или различные паттерны проектирования. Поэтому приходится смотреть по ситуации и соблюдать баланс.
P.S. Если кто не понял — я не за преждевременную оптимизацию и нечитаемые полотна
lain8dono
21.01.2018 23:43Это кстати не про все ЯП. В том же rust функциональщина как минимум так же быстра, как императивщина. Иногда чуточку быстрее. Впрочем rust изначально несёт в себе бо?льшую сложность.
Alex_ME
21.01.2018 23:50Ну тут не только функциональщина же. Вот буквально свежий пример. Делал сему в универе — имитационное моделирование отказов сети. Ну, значит есть граф. И там есть еще всякие параметры служебные для симуляции — исправен ли узел, сколько раз встречается в путях и т.п. По принципу единственной ответственности пихать это в сам граф, который описывает структуру сети, не камильфо. Поэтому перед симуляцией переводилась эта структур в другую, более оптимизированную (ну и ряд других изменений). Добавь я эти поля в граф — было бы менее красиво, но более эффективно.
lain8dono
22.01.2018 01:10Для начала как хранятся данные о связях? Какие части нам требуется оптимизировать? https://en.wikipedia.org/wiki/Graph_%28abstract_data_type%29 (вопросы чисто риторические)
Добавь я эти поля в граф — было бы менее красиво
Я не силён в java, но там нет выбора между хранением по ссылке/по значению?
Всмыслечем отличается
struct Foo { a: i32, b: bool, c: String, } struct Bar { n: isize, foo: Foo, }
От
struct Baz { n: isize, a: i32, b: bool, c: String, }
Alex_ME
23.01.2018 02:25Данные о связях хранятся по-сути в списках смежности.
Суть не в этом. Я не захотел мешать в одних классах данные, которые относились к структуре моделируемой сети (структура графа, названия узлов, интенсивности отказов и прочее) и данные, которые используются исключительно в процессе симуляции и нигде более.
Например есть параметр "важности" узла, ух, не знаю правильный термин.
Попытка объяснитьНу, грубо говоря, центральный узел топологии "звезда" более важен, если он накроется много что. И это не просто количество соседей. Но т.к. по заданию рассматривалась лишь достижимость конечно вершины из начальной, то это — число раз, сколько вершина встречается во всех путях от начальной, до конечной.
khim
23.01.2018 04:03Я не силён в java, но там нет выбора между хранением по ссылке/по значению?
Нет. Несколько примитивных типов (фиксированный список) передаются по значению, остальные — по ссылке.
Одна из вещей, который в C# поправили, когда это была просто «улучшенная Java от Microsoft» (в дальнейшем дорожки двух языков разошлись и современный C# и Java отличаются весьма сильно).myrslok
23.01.2018 14:52Всё передается по значению, но для ссылочных типов значение является ссылкой. Это не то же самое, что передача по ссылке. Например, нельзя написать функцию, которая меняет местами аргументы (в отличие от, скажем, C++, где есть передача по ссылке).
khim
23.01.2018 14:58-1в отличие от, скажем, C++, где есть передача по ссылке.
Это такой очень философский вопрос. Ассемблерный код для «передачи по ссылке» и «по значению через указатель» одинаковы.
Например, нельзя написать функцию, которая меняет местами аргументы
Объясните, пожалуйста, пожалуйста, что вы имеете в виду. Передача по ссылке в C++ даёт вам те же возможности, что и обычная передача для больгинства типов в Java. Вот только передать ссылку наint
в Java нельзя — отсюда костыли…myrslok
23.01.2018 15:08+2Вы заблуждаетесь.
Пример:
String foo = "foo"; String bar = "bar"; swap(foo, bar); // здесь foo приняло значение "bar", а bar" приняло значение "foo".
В языке, где есть передача по ссылке, функцию
swap
написать можно, а в Java — нельзя.lain8dono
23.01.2018 21:25Кстати, на правах рекламы, в rust это делается несколькими вариантами:
let (y, x) = (x, y); // фактически меняет только их имена // но мы можем так делать с разными типами // Или что-то такое use std::mem::swap; swap(&mut x, &mut y); // но типы должны быть одинаковыми swap(x, y); // если x и y сами по себе ссылки // при этом считается, что ссылки не перекрываются во имя оптимизаций конечно же
Плюс там есть replace
Если нам нужно сделать сильное колдунство (например с перекрытием памяти у этих штук), то к нашим услугам такие же, но небезопасные ф-ии из модуля ptr.
khim
24.01.2018 13:19-1Если нам нужно сделать сильное колдунство (например с перекрытием памяти у этих штук), то к нашим услугам такие же, но небезопасные ф-ии из модуля ptr.
Что, собственно, здесь и потребуется. Причём кончится это может тем, что вся ваша программа рассыпется к чертям собачьим.
Проблема не в перекрытии памяти, а в том, что строки в Java — immutable. Соответственно вам нужно будет в swap превратить неизменяемую ссылку на обьект в изменяемую. Да, это можно сделать черезunsafe
— но сольёт всю «прекрасную» безопасностьrust
а в унитаз. Или я ошибаюсь?
Тот факт, что в Javaswap
нереализуем, в rust — требует выхода за пределы «безопасного» подмножества языка, а в C++ — всего лишь парыcast
ов — можно обсуждать… но никакого отношения к способу передачи (по ссылке или значению) это отношения не имеет.
Передайте в вашу функциюswap
в Java вместоString
(который менять нельзя)ArrayList
(который менять таки можно) — и вашswap
преотлично реализуется…myrslok
24.01.2018 13:51Передайте в вашу функцию
swap
в Java вместоString
(который менять нельзя)ArrayList
(который менять таки можно) — и вашswap
преотлично реализуется…Промутировать аргументы — это не то же самое, что поменять их местами. Имелась в виду тождественость ссылок после вызова
swap
, а не равенство поequals
, разумеется.khim
24.01.2018 14:35Имелась в виду тождественость ссылок после вызова
swap
, а не равенство поequals
, разумеется.
Ну, то есть, вы хотите следующего:
Дерзайте.String foo = "foo"; String bar = "bar"; std::cout << "foo: " << &foo << "bar: " << &bar << "\n"; swap(foo, bar); // foo приняло значение "bar", а bar" приняло значение "foo". // Потому следующая строка выведет то же, что и предыдущая. std::cout << "foo: " << &bar << "bar: " << &foo << "\n";
Проверить «тождественность ссылок» в Java нельзя… но в C++-то можно! И видно, что ни о какой «тождественности ссылок» речи не идёт.myrslok
24.01.2018 15:06Проверить «тождественность ссылок» в Java нельзя…
Мне даже немного неловко это писать. Но как это нельзя? А что
==
делает?
На
sdt:swap
вам уже указали и примеры привели. Честно говоря, мне кажется, всего этого должно быть достаточно, чтобы увидеть свою неправоту.
Демонстрация swap со строками// Example program #include <iostream> #include <string> int main() { std::string foo = "foo"; std::string bar = "bar"; std::cout << "foo: " << foo << " bar: " << bar << "\n"; swap(foo, bar); std::cout << "foo: " << foo << " bar: " << bar << "\n"; }
khim
24.01.2018 17:12Мне даже немного неловко это писать. Но как это нельзя? А что == делает?
Она сравнивает содержимое ссылок.
Мутабельность std::string при этом ни при чем, потому что она не используется.
Вы это серьёзно?
Код std::swap вы, кстати, примели неправильный. На самом деле используется std::swap<char, std::char_traits<char>, std::allocator<char>>, но даже если вы специализации не было… Что вторая строчка в приведённой вами функции делает?
lain8dono
24.01.2018 17:18Вы недостаточно знаете rust, чтоб судить о его недостатках и попросту заблуждаетесь. О текущих недостатках оного лучше читать что-то вроде https://github.com/rust-lang/rfcs/tree/master/text (некоторое, что там есть уже исправлено). Но это после чтения доков, спеков и относительно плотного знакомства.
Причём кончится это может тем, что вся ваша программа рассыпется к чертям собачьим.
При неаккуратном использовании. Как и в плюсах. Только в rust ещё и пометочка будет, что тут идёт сильное колдунство, о чём кстати и в документации есть. И без оной не скомпиляется.
Из документации: “trust me, I know what I’m doing.”. И нужно подобное редко.
Соответственно вам нужно будет в swap превратить неизменяемую ссылку на обьект в изменяемую.
Или я ошибаюсь?Если нам требуется делать что-то тёмное с неизменяемыми штуками — у нас скорее всего серьёзная ошибка в дизайне. Ах, да. Строки rust не имеют ни какого отношения к строкам в java.
Или при реализации самого swap? Ну так то разумеется. А как иначе? Но это ведь часть стандартной библиотеки. Оттестировано и проверено.
Ещё по поводу строк в rust. Строка это тип
str
. Или тип&str
, который есть ссылка на неизменяемый по размеру срез массива байт, который находится где-то в памяти плюс некоторое количество валидации для utf-8? Или который всё же&mut str
, где мы можем изменять содержимое? А размер нам нужно менять? ТогдаString
, который в динамической памяти живёт. Сложно, да? Зато работает хорошо. Есть мутабельные и немутабельные для разных контекстов. Документация
в rust — требует выхода за пределы «безопасного» подмножества языка
Чем читаем? В rust есть безопасный швап и для чорной магии. Если нам просто два значения поменять местами — тот, что в mem используем. Если нужно что-то сложнее — это уже априорно чорная магия, которая требует аккуратности. И в этом случае unsafe будет подсказкой, что эту часть кода надо гораздо внимательнее писать и поддерживать.
Алсо о том, как контейнеры/ссылки себя в памяти ведут, есть такая штука https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit (на синенький текст ссылочки на документацию скастовали)
khim
24.01.2018 18:07Вы недостаточно знаете rust, чтоб судить о его недостатках и попросту заблуждаетесь.
Где конкретно?
Строки rust не имеют ни какого отношения к строкам в java.
А где я говорил обратное?
Посмотрите на начало дискуссии: нам демонстрируют какstd:swap
меняет содержимое двух строк, после чего пафосно заявляют: В языке, где есть передача по ссылке, функциюswap
написать можно, а в Java — нельзя.
На мои сначала робкие, а потом всё более настойчивые указания на то, что функция, подобнаяswap
, вообще говоря, требует не только передачи обьектов по ссылке, но, кроме того требуется, чтобы эти обьекты были бы копируемыми (то есть изменяемыми), какомыми строки в Java не являются идёт «уход в несознанку», а потом, после дикого высера это работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен и моего примера, который показывает, что это, мягко говоря, не так — следует заявление в известном духе.
Если нам требуется делать что-то тёмное с неизменяемыми штуками — у нас скорее всего серьёзная ошибка в дизайне.
А с этим, я собственно, и не спорю. Я-то надеялся, что когда я заставлю нашего д’Артаньяна применитьconst_cast
можно будет поговорить о гарантиях безопасности (которая в C++ и Rust обходятся, а в Java — нет), но оказалось, что проблема была намного глубже: похоже автор искренне не понимает в чём разница между передачей параметров по значению и по ссылке.
Если нам просто два значения поменять местами — тот, что в mem используем. Если нужно что-то сложнее — это уже априорно чорная магия, которая требует аккуратности. И в этом случае unsafe будет подсказкой, что эту часть кода надо гораздо внимательнее писать и поддерживать.
Ну если исходить из заявления это работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен — то нам-таки чёрная магия нужна. Что поменять местами две неизменяемые переменные-то!
khim
24.01.2018 13:05Вы заблуждаетесь.
Серьёзно? Давайте пример до конца допишем, а потом уж говорить будем.
Пример:
А вы специально в примере используете не такие строки, как в 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 — нельзя.Free_ze
24.01.2018 13:32допишите в мой пример свою функцию так, чтобы он компилировался и работал
Эта функция уже реализована —std::swap
:
ideone.com/UZ75srint 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"; }
khim
24.01.2018 14:30Контрольный вопрос: в C для вас есть «передача по ссылке» или нет?
Потому что раз уж вы преврашаете std::swap(String&, String&) в std::swap(std::shared&, std::shared&) и заявляете — что это, просто синтаксис такой, на самом-то деле всё так же, как в Java, то и про C'шный вариант:
можно сказать, что это — у нас передача по ссылке, а что там в вызове знаки & стоят — ну так это потому, что в C так принято…swap(&foo, &bar);
Free_ze
24.01.2018 15:05Потому что раз уж вы преврашаете std::swap(String&, String&) в std::swap(std::shared&, std::shared&) и заявляете — что это, просто синтаксис такой, на самом-то деле всё так же, как в Java
Вы пытаетесь доказать свою точку зрения с помощью семантически разного кода. Я «превратил» этот код в адекватный Java-семантике.
можно сказать, что это — у нас передача по ссылке, а что там в вызове знаки & стоят — ну так это потому, что в C так принято…
Можно. Тогда соглашусь, что условно «передачей по ссылке» в Java можно считать костылики с одноэлементными массивами и прочими мутабельными обертками (= Возможно еще что-то изsun.misc.Unsafe
(если еще не выпилили). Остальное все — передача по значению, ибо никакой другой возможности ссылаться на ссылки в Java нет.khim
24.01.2018 17:39Вы пытаетесь доказать свою точку зрения с помощью семантически разного кода. Я «превратил» этот код в адекватный Java-семантике.
Проблема в том, что «передача по ссылке» vs «передача по значению» — это не о семантике. Это о синтаксисе.
В частности, считается, что передачи по ссылке в C нет, а в C++ — есть. Но ведь легко убедиться, что C'шнаяvoid swap(int*, int*);
и C++'наяvoid std(int&, int&);
не просто «близкие родственники»! На уровне машинного кода они просто идентичны! Более того, при использовании ICF — это вообще будет одна и та же функция!
Остальное все — передача по значению, ибо никакой другой возможности ссылаться на ссылки в Java нет.
Что это за передача по значению такая, при которой обьект (если он, конечно, mutable) может быть испорчен? Хочу вам напомнить чем вообще эти два варианта отличаются: ри вызове по значению, выражение-аргумент вычисляется, и полученное значение связывается[en] с соответствующим формальным параметром функции (обычно посредством копирования этого значения в новую область памяти). При этом, если язык разрешает функциям присваивать значения своим параметрам, то изменения будут касаться лишь этих локальных копий, но видимые в месте вызова функции значения останутся неизменными по возвращении
В Java при передаче изменяемых типов в функцию вы никогда не можете быть уверены, что она не испортит вам эту переменную — за исключением примитивных типов.
Можно, конечно, «расщепить» типы данных и ссылки на эти типы данных и говорить о том, что ссылки передаются по значению… но учебники Java так не делают и, как уже говорилось выше, передача по значению отличается от передачи по ссылке в первую очередь синтаксически, а вот семантической разницы может и не быть (см. указатели в C и ссылки в C++).Free_ze
24.01.2018 17:55Проблема в том, что «передача по ссылке» vs «передача по значению» — это не о семантике. Это о синтаксисе.
Разница между объектом в стеке и указателем на объект в хипе очень даже семантическая.
Что это за передача по значению такая, при которой обьект (если он, конечно, mutable) может быть испорчен?
По значению вы передаете ссылку, которая копируется. Поэтому ссылку повредить/переназначить вы не сможете никак. В родственном C# для этого есть ref/out-модификаторы, что подчеркивают эту разницу. В Java же распространенной практикой (по крайней мере Хорстман с Кеем об этом пишут) является обёртывание объекта в массив единичной длины.
Можно, конечно, «расщепить» типы данных и ссылки на эти типы данных и говорить о том, что ссылки передаются по значению… но учебники Java так не делают
Потому что это учебники про язык, а не про платформу.myrslok
24.01.2018 18:04Потому что это учебники про язык, а не про платформу.
На самом деле учебники, несомненно, "так делают", т.е. различают объекты и ссылки (включая учебники начального уровня, например, Head First).
Более того, это в явном виде написано по ссылке выше ("… в сообществах Java и Visual Basic ту же семантику часто описывают как «вызов по значению, где „значением“ является ссылка на объект»...").
khim
24.01.2018 18:21Разница между объектом в стеке и указателем на объект в хипе очень даже семантическая.
Да, но вопрос передачи аргументов по ссылке или значению — он немного ортогонален, хотя в Java они связаны: все обьекты, размещённые в стеке передаются по значению, все обьекты, размещённые в куче — по ссылке.
В C++ — это не так, можно передавать по значению как обьекты размещённые в стеке, так и обьекты, размещённые в куче — и по ссылке тоже можно передавать и те и другие.
Но и в Java и в C++ для функции, подобнойswap
мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!Free_ze
24.01.2018 18:44в Java и в C++ для функции, подобной
Если мы хотим изменить состояние объекта, то нельзя. Но если мы хотим поменять местами объекты (в чем и кроется смыслswap
мало того, чтобы обьект был передан по ссылке — нужно ещё, чтобы он был изменяем!swap
), то в C++ это делается запросто чистыми синтаксическими средствами, в Java — только неочевидными хаками с целью преодолеть и превозмочь.khim
24.01.2018 19:22Но если мы хотим поменять местами объекты (в чем и кроется смысл
Нет, нет и нет.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, определённой для неё. В языке для этого ничего менять не потребуется.Free_ze
25.01.2018 12:28Это понятно, мутаторную форму
std::swap
дляstd::basic_string
мы условились не использовать.
Но сути не меняет: в Java строки — это объект ссылочный, полностю лежащий в хипе. Поэтому хорошего аналогаString foo = {"foo"};
там не сделать. Ну вообще никак. (Более того, сами строки размером доsizeof(_CharT*)
в типичных имплементациях аллоцируются на стеке) Поэтому просто ссылки не катят. Нам нужны указатели и полностью хиповый объект! С чего мы и начали этот длинный тред.khim
25.01.2018 13:12Это понятно, мутаторную форму
Тем не менее с неё и начался весь этот субтред.std::swap
дляstd::basic_string
мы условились не использовать.
Поэтому просто ссылки не катят. Нам нужны указатели и полностью хиповый объект! С чего мы и начали этот длинный тред.
Нет. Нужны ссылки. Союственно «иностранное» название call-by-reference как бы намекает.
Полным аналогом версии из Java в C++ будет следующее:
const std::string& foo = std::string("foo"); const std::string& bar = std::string("bar"); std::swap(foo, bar);
И, сюрприз, сюрприз, сюрприз: это — тоже не работает. Даже не компилируется.Free_ze
25.01.2018 13:28Тем не менее с неё и начался весь этот субтред.
Краткий экскурс по треду, для тех, кто только пришел: я поправил человека и даже как-то косвенно говорил о том, что он был не прав. Далее я привел код, с которым мой оппонент так же не был согласен.
Полным аналогом версии из Java в C++ будет следующее
У вас мутабельный объект в стеке. Попробуйте еще раз.
Free_ze
25.01.2018 14:00Нет. Нужны ссылки. Союственно «иностранное» название call-by-reference как бы намекает.
Там call-by-reference и происходит) Указатели передаются по ссылке. Только в Java «безопасные указатели» называются ссылками.
- Создаёт временный обьект типа
poxvuibr
25.01.2018 10:21Да, но вопрос передачи аргументов по ссылке или значению — он немного ортогонален, хотя в Java они связаны: все обьекты, размещённые в стеке передаются по значению, все обьекты, размещённые в куче — по ссылке.
В Java все переменные передаются по значению. Только значение переменных, указывающих на объект это, как несложно догадаться, указатель :)
khim
25.01.2018 13:32+1В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на C++ тоже принимает не обьекты, а ссылки на объекты (вот тут даже её код в три строки приведён) и она эти ссылки не меняет (собственно в C++ и нет вообще никаких методов, позволяющих изменять ссылки).
Что гораздо хуже — в языках типа Algol'а и Pascal'я, откуда пошли эти термины («вызов по ссылке» и «вызов по значению») в функцию, которая описывает параметр не какx : Integer
, а какvar x : Integer
тоже передаётся не обьект, а ссылка.
Рассмотрите следущий код для языкаPascal
(учили в школе, аль нет?):
procedure foo(var a : String, var b : string); begin swap(a, b); end;
Рассмотрите этот пример. Это — самое эталонное, самое «true», «лошадь ростом в метр и весом в один килограмм» pass-by-reference. То, с чего пошла вся терминология. До того, как в языка программирования появилась куча и даже стек (да-да — в ранних языках программирования стека не было).
И что же мы тут видим? Правильно: мы видим две ссылки на объекты типаString
, которые при вызове функцииswap
копируются — после чего функцияswap
их изменять не может, но может изменять то, на что они указывают!
Почему, блин, вещь, которая полвека назад называлась pass-by-reference, транслируясь почти в такой же байткод как в Java (да-да, вы не поверите, но Pascal тоже транслировался в байткод, как и предшественник C, BCPL) стала вдруг называться «pass-by-value». С какого, я извиняюсь, перепугу?poxvuibr
25.01.2018 14:25В этом случае у вас вообще ни в каком языке передачи значений по ссылке не будет. Ибо версия на C++ тоже принимает не обьекты, а ссылки на объекты (вот тут даже её код в три строки приведён) и она эти ссылки не меняет (собственно в C++ и нет вообще никаких методов, позволяющих изменять ссылки).
Вот вам код на C++, демонстрирующий, что есть в Java, а чего нет
Код под спойлером#include <iostream> using namespace std; class TestStruct { public: int id; int count; TestStruct(int id, int count) { this->id = id; this->count = count; } TestStruct(const TestStruct& obj) { this->id = obj.id; this->count = obj.count; cout <<"copy constructor working " << std::endl; } }; int main() { { TestStruct s1(1,1); TestStruct s2(2,2); cout <<"copy constructor will be invoked, java can do something like that" << std::endl; std::swap(s1, s2); //Java can do something like that cout <<"s1.id should be 2 and is " << s1.id << std::endl; } { TestStruct* s1 = new TestStruct(1,1); TestStruct* s2 = new TestStruct(2,2); cout <<"References will be copied, java can't do that" << std::endl; //Java can't do that std::swap(s1, s2); cout <<"s1->id should be 2 and is " << s1->id << std::endl; } return 0; }
myrslok
24.01.2018 13:39А вы специально в примере используете не такие строки, как в Java?
Не понял, что вы имеете в виду. Приведенный (невозможный) код написан на Java.
Дописывать и вообще разбирать ваш код мне неудобно и недосуг, извините. Вот (найденный) код с использованием
std::swap
:
#include <iostream> using namespace std; int main() { int a, b; cin >> a; cin >> b; swap(a, b); cout << a; cout << b; return(0); }
В Java так сделать нельзя, что с int, что с Integer.
Повторюсь, если вы думаете, что в Java агрументы передаются по ссылке, вы заблуждаетесь, причем заблуждаетесь капитально. Рекомендую погуглить что-то вроде "is Java pass-by-value". Например, на Stackoverflow это подробно разобрано. Ну или вот (часть 8.4.1 спецификации Java 8):
When the method or constructor is invoked (§15.12), the values of the actual argument expressions initialize newly created parameter variables...
khim
24.01.2018 14:21Я боюсь это становится вопросом терминологии. Можно либо считать, что в Java всё и всегда передаётся по ссылке, но взять «ссылку на ссылку» нельзя, потому что обьекты, размещённые на стеке, недоступны GC, либо разделять обьекты (хранящиеся в куче) и ссылки на них (размещённые на стеке).
В первом случае — параметры у нас таки передаются по ссылке, но агрументом является обьектString
(как, собственно, в коде и написано), либо, альтернативно, что есть ещё и ссылки где-то (которых в коде, в общем-то, не видно, они для нас как тот «суслик») — тогда передача происходит по значению, но всё окончательно запутывается.myrslok
24.01.2018 14:32-1Судя по тому, что вы написали выше ("Передача по ссылке в C++ даёт вам те же возможности, что и обычная передача для больгинства типов в Java."), у вас была содержательная, а не терминологическая ошибка в картине мира.
А что до терминологии, то она вполне стандартна, и "считать, что в Java всё и всегда передаётся по ссылке" нельзя.
khim
24.01.2018 17:17у вас была содержательная, а не терминологическая ошибка в картине мира.
Извините, но что-то не так в вашей картине мира, а не в моей. Ибо заявление:то работает именно потому, что передача идет по ссылке, а не потому, что int мутабелен
это что-то за гранью добра и зла.
khim
24.01.2018 14:27В Java так сделать нельзя, что с int, что с Integer.
В Java так сделать нельзя, не потому, чтоInteger
передаётся по значению, а потому что он Immutable.Mutable
вариант — это, напримерint[]
с одним элементом.
Не понял, что вы имеете в виду.
Во всех ваших примерах на Java вы используетеimmutable
типы. И изменить их нельзя не потому, что они «переданы по значению», а потому что они, в принципе, неизменяемы.
В моём примереString
— тоже неизменяемый и, внезапно, его «так просто» изменить нельзя — даже при передаче по ссылке.myrslok
24.01.2018 14:44Изменяемость типа с этими вопросами не связана, см. другой мой коммент. К инстансу
ArrayList
указанные аргументы применимы в равной степени.
std::swap
именно меняет ссылки, а не изменяет каждое из значений. В приведенном мной коде, кстати,int
. И это работает именно потому, что передача идет по ссылке, а не потому, чтоint
мутабелен.khim
24.01.2018 17:26И это работает именно потому, что передача идет по ссылке, а не потому, что
В вашей программе обьявить их иммутабельными нельзя, но можно её немного модифицировать — и всё. std::swap перестаёт работать.int
мутабелен.
Ещё раз, для идиотов. Для работоспособности функций, подобныхstd::swap
нужно выполнение не одного, двух условий:
- Передача аргументов в функцию должна происходить не по значению, а по ссылке.
- Переданные значения не должны быть
immutable
(для них должен быть определёнoperator=
).
Только при выполнении обоих условий функция, подобнаяstd::swap
реализуема. Для примитивных типов в Java не выполняется первое условие, для типовInteger
иString
— второе, но возьмитеArrayList
— и всё получится!myrslok
24.01.2018 17:36Ещё раз, для идиотов.
Уверен, что вы кругом неправы в основах, но разговор окончен.
ad1Dima
22.01.2018 06:04Ну хоть бы взять пример выше — LINQ позволяет сделать код куда компактнее, чище, проще для понимания, но ухудшает эффективность.
В чем LINQ ухудшает эффективность? Когда он плохо в sql смаплен?leotsarev
22.01.2018 07:50Если говорить про Linq-to-Objects — многовато лишних аллокаций.
ad1Dima
22.01.2018 07:55Кстати, тут на днях появилось (сам еще толком не смотрел) github.com/kevin-montrose/LinqAF
Zelgadiss
21.01.2018 22:57Спорное мнение. В итоге производительность программы теряется в угоду усиленной читабельности кода.
lair
21.01.2018 23:41Во-первых, не всегда. Во-вторых — ну да, иногда теряется. Это далеко не всегда настолько важно.
bormotov
21.01.2018 23:57производительность процессоров растет быстрее, чем производительность людей.
В тех 7% где не растет — будет не тупой код, и комментарий «не трогайте тут, выигрываем несколько тактов»Splo1ter
22.01.2018 15:31Ну, как совет при оформлении PR в ASP.NET Core, пишут в гайдлайнах избегать Linq в hot-path частях.
andreycha
23.01.2018 17:44Потому что там это имеет смысл. Но подавляющему большинству приложений далеко до ASP.NET Core в плане «горячности» hot path'ов. А каким-то это и вовсе неважно.
september669
22.01.2018 16:48Этот распространенный принцип очень нравится когда пишешь код, а вот когда используешь такие программы, то наоборот. Особенно, когда их надо несколько одновременно запускать.
Mendel
22.01.2018 17:00+3Это только так кажется.
На самом деле ровно наоборот.
Именно «умные» программы как правило и тормозят, поскольку по мере роста всю картинку не видит никто, и начинается нагромождение на нагромождение и так далее.
А простая структура, в которой каждый элемент прост и «понятен даже идиоту», там и доработать узкое место проще.
В сферическом проекте где время бесконечно — конечно лучше оптимизировать всё. А в реальном — всё что ты потратишь на преждевременную нанооптимизацию тебе придется откуда-то взять. И либо придется пожертвовать (выпустить много позже) фунционалом, либо не оптимизировать именно то что нужно.Free_ze
22.01.2018 17:04Именно «умные» программы как правило и тормозят, поскольку по мере роста всю картинку не видит никто, и начинается нагромождение на нагромождение и так далее.
Это решается абстракциями. Микрооптимизации на то и микро, что на общую архитектуру не влияют и живут в своих четко очерченных, хорошо закоментированных областях.
bormotov
22.01.2018 17:01не понимаю в чем проблема.
Если при написании программы, у вас спрашивали требования (нужно что бы быстро, нужно что бы несколько штук одновременно), то значит, нужно идти к авторам и тыкать пальцем «вот тут не соответствует».
Если же это массовый продукт, то видимо есть некий механизм, для обратной связи, и можно верить, что этот механизм рабочий.
Чудес, к сожалению, в этом месте не бывает. Вот, у меня в списке дежурных фраз есть
Тупые решения выигрывают у умных в 9 из 10 случаев. Будьте достаточно умны, что бы предлагать тупое решение
Практически вся эта статья в одной фразе.
boblenin
22.01.2018 18:44Ну вот я обновил 3770k на 8700k — по бенчмаркам разница 2x. Это 6 лет прошло. С одной стороны за 6 лет новичок вполне обучается в опытного разработчика и его производительность более чем удваивается. С другой стороны дополнительных абстракций в софт за 6 лет добавлено вполне достаточно, чтобы двукратный рост производительности процессоров нейтрализовать. И это если не считать ущерб от meltdown.
Alex_ME
23.01.2018 02:36Так-то да, а потом ты запускаешь Atom на ноутбуке старше 3х лет и начинаются боль и страдания.
africaunite
22.01.2018 01:01По опыту — на производительность больше всего влияют архитектурные решения. И простой и очевидный код очень важен именно этим — очевидностью и простотой при рефакторинге. Проще расширить мост с "банальной" конструкцией, чем тот, в котором трудно рассмотреть на чем все, на самом деле, держится.
До середины 90-х нам иногда приходилось оптимизировать и коэффициент k в алгоритмах с линейной сложностью O(kN), но и тогда, когда компьютеры были бледнее "Малины", кроме того, что такие приемы были редки и очевидны — очень важно было следить за "внятностью речи".
Ohar
22.01.2018 02:27Производительность программы действительно бывает важна обычно всего в нескольких местах, которые лучше оформить как отдельный чёрный ящик.
AllexIn
22.01.2018 12:39Круто, если в приложении производительность зависит от алгоритма, который легко отделяется в черный ящик. Хуже, когда этот алгоритм через весть проект идет.
Mendel
22.01.2018 14:13Неее. Нормально когда ЛЮБАЯ часть приложения превращается в черный ящик и все приложение состоит из черных ящиков (которые впрочем на самом деле белые).
И кошмар когда этого нельзя сделать.
Вроде смысл не сильно меняется, но акценты именно так стоят ибо как правило невозможность разнести в черные ящики это проблема архитектуры.
poxvuibr
22.01.2018 15:26Тупой код обычно состоит из каких-то стандартных паттернов и стандартных приёмов. Таких приёмов, которые умеют находить и распознавать и оптимизировать компиляторы, интерпретаторы и JIT.
your_eyes_lie
21.01.2018 22:57+1Для ленивых: YAGNI, принцип единственной ответственности, DRY, принцип единственного уровня абстракции, слабое зацепление, сюда ещё подойдёт, например, KISS
KirEv
21.01.2018 23:34в прошлом месяце поймал себя на мысли: сделать просто — сложно.
этот крутой чувак, из мира Java, кажется говорил не про метод использование if, switch, etc., а про метод реализации конкретных алгоритмом, реализация которых в чистом коде дает понять при чтении: это функция f, они принимает аргумент x и возвращает y, и в тупом коде ясно как день без лишних комментариев каким образом c x сделался y.
… другой вопрос — именование, не менее сложная задача чем писать простой код…
да и вообще, одна из любимых фраз: простые вещи ломаются реже сложных.
lxsmkv
22.01.2018 00:37А называю это не «тупой», а «скучный» код. Код в котором нет никаких неожиданностей, ничего неочевидного, нет никаких крутых финтов, никаких исключений из правил, никаких нарушений архитектурного шаблона, никаких сложных абстракций, код, который может понять даже человек не знакомый с этим конкретным языком программирования.
Acuna
22.01.2018 08:21код, который может понять даже человек не знакомый с этим конкретным языком программирования
Не к этому ли нужно стремиться?sand14
22.01.2018 12:49код, который может понять даже человек не знакомый с этим конкретным языком программирования
Не к этому ли нужно стремиться?
Вряд ли к этому.
Допустим, мы пишем чистый и понятный код.
При этом к месту используем лямбды, LINQ (C#) или Stream API (Java).
Но ведь до сих пор много разработчиков не освоили функциональный подход, хотя он есть в C# аж лет 10, и почти 4 года в Java.
Что же, нам не использовать его, чтобы все могли прочитать?Neikist
22.01.2018 13:17Понять что есть лямбды нужно один раз, и дальше этот разработчик вполне будет читать код, так что код будет все еще прост. А вот всякие нагромождения тернарных операторов в тернарных операторах, или десятки параметров метода на километр — когнитивную нагрузку создают очень значительную, и каждый раз когда встречаются, и неважно что концепция разработчику знакома. Я бы уточнил формулировку из комментария выше:
код, который может понять даже человек не знакомый с этим конкретным языком программирования
, в случае знания концепций с использованием которых он был написан.Mendel
22.01.2018 13:53Давайте оставим солонку уязвимой а общипанную курицу — человеком. Ну и так же ясно о чем речь). А то сейчас добавим «и при знании основ синтаксиса»… потом отрефакторим, добавим, уберем, опять добавим, опять перепишем и покроем тестами…
lxsmkv
23.01.2018 02:40Хотя я должен заметить, что малость загнул. Сказать сказал, а вот как это в жизни выполнить… правильно, можно только стремиться к этому. Я и сам бывает ляпну где нибудь так, а где-то эдак. Так что, не мне камни кидать, так сказать. Ведь и мне кажется в тот момент, что так сейчас удобно, понятно, и очевидно. А бывает просто ради эксперимента какую-то конструкцию напишешь. А вот удивлюсь ли я этому коду через три месяца — не знаю.
Сегодня тупил над кодом в продукте (на яве, переменные все числовые, названия изменены):
сам пишу в основном на питоне (GUI-тесты) и в нем можно соединять логическими операторами числа и булевы значения. А в яве это невозможно, и поэтому скобки посчитали ненужными. Я спросил, а что сложного поставить скобки, да и вообще дать обозначение каждому условию и вынести их в переменные, так и логгирование будет проще делать, если придется. Мне сказали, ну, да можно делать, можно не делать. Придумывать название переменным мол то еще мучение.return x1 > x2 && y1 == y2;
А потом я же слушаю от этих ребят какой у них нечитаемый код, и что там черт ногу сломит. Год назад они говорили как будет здорово, если старый код можно будет выкинуть и написать все заново. Вот у них появилась возможность. И что мы видим через год — те же яйца только в профиль. Даже не знаю, что сказать.
sand14
22.01.2018 22:25А вот всякие нагромождения тернарных операторов в тернарных операторах, или десятки параметров метода на километр
Опять же, из практики — такое как раз встречается в коде, который состоит из последовательных процедурных портянок, позиционирующихся как простой и понятный код.
Acuna
23.01.2018 02:03То, что Вы описали — базовые навыки, которые должны быть знакомы каждому джуниору, это как в веб-разработке постигать азы верстки. Поэтому если джуниор еще не в курсе об этих технологиях и пишет код без них (что, к слову, наоборот усложняет код в случае без тех же лямбд), то это остается исключительно на совести этого джуниора. Однако лично для меня сложный код тот, который подчас неоправданно усложнен, и не в плане технологий, а именно в плане того, что многие вещи можно было бы написать намного проще, что, как следствие, привело бы к лучшей читаемости программерами всех уровней, эдакая кроссплатформенность в плане опыта, а кроссплатформенность — это всегда хорошо) Большей частью я и занимаюсь рефакторингом лапшекода от таких вот писателей, и у меня возникает чувство, что код пишется как-то сам собой, а они в этот момент находятся в каком-то другом месте, в любом случае, о простоте и читаемости они явно не думают, однако писать просто — это сложно, я знаю по себе. Часто я даже хожу по офису и обдумываю как я могу написать что-либо проще. А потом еще проще. Можно наговнокодить, а можно потратить на тридцать минут времени больше и родить код, действительно понятный даже ребенку. Но это нужно любить работу и проект, возможно в этом и дело. Ну и плюс иметь возможность вот так ходить и размышлять о своем. Поэтому чаще всего на выходе получается «хоп-хоп и в продакшн» с кучей костылей. Но работает ведь, так что лучше не трогать. А то, что там все упадет когда кто-то оставил коммент — дак поэтому же лучше и не трогать)
sand14
23.01.2018 10:22То, что Вы описали — базовые навыки, которые должны быть знакомы каждому джуниору, это как в веб-разработке постигать азы верстки. Поэтому если джуниор еще не в курсе об этих технологиях и пишет код без них (что, к слову, наоборот усложняет код в случае без тех же лямбд), то это остается исключительно на совести этого джуниора.
Ох, да в моей практике как раз сеньоры не хотят "учить" лямбды и ФП.
Acuna
23.01.2018 21:26Вообще, если говорить уж совсем на чистоту, лямбды действительно делают код короче, но никак не проще. В прошлом комменте под усложнением я подразумевал именно увеличение его длины. Для меня код является сложным, когда на его чтение затрачивается больше времени, чем могло бы. Лямбды в данном случае делают код короче, и его чтение осуществляется быстрее, однако сами по себе они имеют в себе магию, которая, очевидно, явно не способствует его очевидности и простоте. Поэтому я все-таки я уже начал смотреть выше джуниора, однако если мы говорим все-таки о простом и понятном коде, тут все-таки стоит смотреть на него глазами джуниоров, благодаря чему мы и получим простой код, но все же уже без «лябд и ФП».
sand14
23.01.2018 23:29+2Чтобы понять лямбды, достаточно знать, что лямбда это не код, который выполняется непосредственно в месте написания, а правило — математическая формула, которая будет использована как паттерн стратегия.
И в этом смысле не так уж сильно лямбда отличается от простой процедуры.
Процедура это блок кода, который тоже выполняется не в месте его написания, а где-то в другом месте при его вызове.
Если исходить из мифа, что из математиков получаются лучшие программисты, то вообще неясно, какая может быть трудность с лямбдами (это же формулы).
Также неясно — уже столько лет носятся с паттернами (на одном хабре уж лет 10 как, а если копнуть глубже, то и сильно побольше), а самый понятный и полезный паттерн стратегия, позволяющий реализовывать декомпозицию и инверсию контроля, так и не осилили (хотя взять тот же DI — тоже стратегия в чистом виде, только прокидываемая не в виде лямбд, а в виде интерфейсов).
Получается, давно пишем на мультипарадигменных языках, включающие ОО и ФП, а на деле копни — вся индустрия (за всех, конечно, говорить, не будем, но тем не менее) пишет и защищает процедурные портянки.
ZyXI
24.01.2018 01:04Меня бы объяснение, что лямбда — это оказывается реализация паттерна стратегия только больше бы запутало. Лямбды — это ссылки на функции, функции определяются прямо на месте. Если вам всё ещё не понятно, представьте код лямбды, который вынесли в отдельный метод, а его вызов вставили в место вызова лямбды, но не в место её определения.
А зачем носятся с паттернами на хабре я не знаю, я как?то прочитал их описание, понял, что мой мозг может сгенерировать что?то подобное и без знания, что это какой?то паттерн, и благополучно забыл бо?льшую часть прочитанного про паттерны. Учитывая, как «часто» мне потом приходилось слышать про паттерны от иностранных коллег во время собственно работы над OS проектами (Python сначала, C и lua сейчас), а также от русских коллег во время работы на собственно работе (LabVIEW, C и ассемблер), могу заключить, что паттерны мне нужны только на уровне «тот парень сказал „visitor“ и я понял, что он сказал». Конечно, ассемблер не особо располагает к применению шаблонов, но про остальные я такого не скажу.
DistortNeo
24.01.2018 01:38Я бы тоже ничего из такого объяснения не понял.
Почему лямбда — не код, когда это код? Лямбда — это синтаксический сахар ЯП для классов-функторов. А весь этот матан про лямбды хорошо понятен только чистым математикам-теоретикам, программистам-практикам он только мешает.
Без захвата внешних переменных лямбда — это просто сокращённая запись для функции, с захватом — уже более сложная штука с магией. Но понимать такой код эта магия совершенно не мешает.
Ну и аналогия с паттерном «стратегия»: я ни разу не видел, чтобы этот паттерн оформляли именно таким образом (т.е. с именами, содержащими Strategy). Поэтому такое объяснение тоже было бы непонятно.VolCh
24.01.2018 09:10я ни разу не видел, чтобы этот паттерн оформляли именно таким образом (т.е. с именами, содержащими Strategy)
Всё же встречается. Как правило в случаях когда паттерн используется сознательно. Впрочем, это и к другим паттернам относится.
sand14
24.01.2018 10:44+1Почему лямбда — не код, когда это код? Лямбда — это синтаксический сахар ЯП для классов-функторов.
Вы сейчас описали реализацию (одну из возможных) лямбд.
Понимать реализацию тоже нужно, но все же лучше понимать, что значит каждая из концепций по своей сути.
А то ведь в любом высокоуровневом языке программирования всё — сахар, начиная от процедур и базовых типов (булевых, массивов, строк).
lair
24.01.2018 09:36Лямбды — это ссылки на функции, функции определяются прямо на месте.
В C#, скажем, это не совсем так (приблизительно начиная с того места, где деревья выражений).
sand14
24.01.2018 11:31А зачем носятся с паттернами на хабре я не знаю, я как?то прочитал их описание, понял, что мой мозг может сгенерировать что?то подобное и без знания
К слову, если смотреть периодическую литературу, то носиться с паттернами, включая MVC, MVVM и иже с ними (визиторы, обсерверы и прочая), начали лет 20 назад (тогда же началась пропаганда аджайла и скрама).
А так то, все началось еще раньше — вспомнить историю того же SOLID.
И что всегда удивляло — если действовать по здравому смыслу, то так и так получаются SOLID и все прочие паттерны.
Это под силу вывести даже одному человеку.
И я не против того, чтобы подобные подходы были формализованы в виде унифицированных доступных всем описаний. Только за.
Только пока получается так, что разговоров больше, а применения паттернов на практике — меньше.
Смотришь какой-нибудь MVVM-проект — да, формально MVVM есть, т.к. фреймворк обязывает, да еще DI прикручен, а в коде почему то все равно — сильная связность и лапша.
Mendel
24.01.2018 13:17Если вам всё ещё не понятно, представьте код лямбды, который вынесли в отдельный метод, а его вызов вставили в место вызова лямбды, но не в место её определения.
А почему тогда нельзя было вот прям так и сделать?
Вот просто взять и вынести в отдельный метод. почему нет?
Я спрашиваю не потому что «тому кто не понимает в лямбдах будет проще читать». Просто хорошим тоном является выделять каждую логическую единицу в отдельный метод. При этом большинство кодгайдов дают рекомендации «метод должен быть не больше чем хх LOC». А лямбда которая могла бы быть отдельным методом — у нас его тупо расширяет.
Я вижу только две причины:
1) лямбда ну очень короткая
2) сложно придумать этому методу говорящее название
Однако пункт два является явным поводом задуматься, а не с запашком ли у нас код…alexeykuzmin0
24.01.2018 13:35Лямбда замечательно подходит в случае, если она является деталью реализации конкретного метода. Тогда логично ее и написать внутри этого метода, скрывая от всех снаружи (в том числе и от других методов этого класса).
Mendel
24.01.2018 14:33При условии что она маленькая.
Меня тут позвали в один проект который я одиннадцать лет запускал, потом продал, потом дорабатывал по контракту, потом дикие люди к нему кучу всего понаписывали и вот вдруг надо «чуть-чуть доделать» ну и «просто посмотреть насколько оно нормально».
Я на это посмотрел и сказал что пока там не приберутся я даже смотреть не буду (ага, вот так я и смогу не смотреть). Теперь две недели войны на тему «ну почему же мне надо разделять метод экшена в контроллере, ну и что что он на 270 LOC, ну и что что это ТТУК и вообще пять вложений ифов это перебор, но ведь это же часть реализации, оно будет лучше читаться если будет внутри, и скрыто от всех. Я плюнул на то чтобы что-то объяснять, просто накатал план по рефакторингу с жесткими метриками где не надо думать а тупо смотреть — IDE сказала что в этом методе больше 20 строк — дели. Зачем — не твоя забота. Как — посоветую.
Только после того как поверх этого появилась „виза“ закачика о том, что пока не будет сделано з/п не видать — все начало двигаться.
К чему я это? Мы конечно можем сказать что лямбды и тернарники улучшают читаемость в тех местах где они уместны, но… тот кто понимает разницу — в таком совете не нуждается, а тому что не понимает — нужны более детерминированные метрики.
С тернарниками я для себя решил „только если результат константа, или один вызов одного метода и колво параметров у методов не больше двух“.
С лямбдами — »если общий размер метода будет укладываться в лимит, и я понимаю как я бы я назвал метод если бы он не укладывался и я бы вынес".alexeykuzmin0
24.01.2018 14:36При условии что она маленькая.
Да, при условии, что она маленькая. Потерял эту фразу при рефакторинге комментария.
Разумеется, в первую очередь нужно думать головой и смотреть, получается ли код читаемым (или, возможно, в этом конкретном месте что-то другое важнее читаемости), и отталкиваться уже от этого.VolCh
24.01.2018 14:38+1Тогда это не рефакторинг :)
alexeykuzmin0
24.01.2018 14:42Не согласен. Это рефакторинг, потому что задумывался как изменение формы на более читаемую без изменения смысла. Но с багой, потому что смысл все-таки изменил.
Chaos_Optima
24.01.2018 19:43+1Вот просто взять и вынести в отдельный метод. почему нет?
- Короткая
- Используется 1 раз
- Замыкание
Acuna
23.01.2018 05:42Код в котором нет никаких неожиданностей, ничего неочевидного, нет никаких крутых финтов, никаких исключений из правил, никаких нарушений архитектурного шаблона
Да и вообще, с этим нужно аккуратнее. Программист должен думать не о том, чтобы показать работодателю какой он крутой, а думать об оптимальности. Можно и гвозди кувалдой забивать, ибо из-за своего веса с одной стороны она более эффективна, только вот устанет человек быстрее чем забьет гвоздь. Плюс скорее всего раздербанит и поделку и сам гвоздь. А можно взять молоток. Он легкий и простой, и создан специально для этого. Другой вопрос что можно и им пальцы отбить, но это уже и вовсе зависит от подготовки джуниора, все-таки на работу не профанов обычно берут.
leventov
22.01.2018 01:08Сеньор отличается от новичка не качеством кода (новичок тоже может написать хороший код), а просто опытом. Сеньор много чего видел, чувствует какие проблемы могут всплыть с проектом и т. д.
Mendel
22.01.2018 14:01Ну я бы сказал что статья чуть утрированна и речь скорее идет о юниоре и мидле.
Чтобы быть юниором нужно знать язык/платформу, быть знакомым с инструментами и уметь написать хелловорд.
Чтобы быть мидлом необходимо владеть основными принципами «простого» кода на уровне «могу выполнять», уметь соблюдать код-гайды и иметь уверенные навыки работы с инструментами. Для этого уже нужно немного опыта (в идеале с наставником, так быстрее).
Сеньору же да, нужно много опыта чтобы не просто уметь использовать парадигмы и инструменты но и четко понимать почему нужно именно так а не иначе (мидлу хватит и «потому что БОСС так сказал»).
punkkk
22.01.2018 14:32И в целом, в случае возникновения нежданчика сеньор, обычно, знает что делать.
Ну еще ЗП у них разные, если уж на то пошло. :DMendel
22.01.2018 14:51У сеньора не бывает нежданчика. Сеньор ЗНАЕТ что жопа рано или поздно случится, и все время к ней готовится. Поэтому и знает что делать).
africaunite
22.01.2018 15:01В большинстве видов деятельности человека — уровень "сеньорности" определяется, в первую очередь, стабильностью определенного качества результата.
Rambalac
22.01.2018 11:20-1Сеньор-помидор там или нет. Но реально замечаю, что за новичками, или точнее сказать индусами или вьетнамцами, основной способ улучшения или даже исправление их кода, это в основном удаление по Ctrl+l.
Другая их большая проблема, это совершенное непонимание, что будет с их кодом написанном в одном таске буквально на следующий день в их же следующем таске. Простейшее решение для них, это копипаста и последующее исправление всех (или нет) копипаст в случае изменений.fatronix
22.01.2018 23:09-1Не знаю, за что человека заминусовали, но в работе с индусами (не образными) действительно всё так.
SirEdvin
22.01.2018 12:40А потом это все выходит в «нам на приложение, которое читает rss нужно 5 серверов».
Далеко не всегда получается добиться простого кода и нормальной производительности без явных боттнеков.
arturpanteleev
22.01.2018 12:50Перечитал статью пару раз, но так и не уловил посыл. Автор хотел сказать, что не стоит увлекаться чрезмерным следованием популярным архитектурным принципам, или наоборот ратует за то, что их нужно придерживаться?
Free_ze
22.01.2018 13:48Автор говорит, что всё хорошо в меру, нужно руководствоваться читабельностью и здравым смыслом, а потом уже популярными архитектурными принципами.
sand14
22.01.2018 12:55В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом (кстати, тогда не стоит называть его тупым):
Неслучайно появился первый комментарий — конечно, код в нем утрирован (хотя… и подобное доводилось видеть в рабочих проектах).
Дело в том, что очень часто под таким кодом (чистым и понятным, в терминах статьи — тупым) понимается последовательная процедурная портянка, которую кто угодно прострочно прочитает.
Вряд ли такой можно назвать хорошим.Free_ze
22.01.2018 13:51В статье не раскрыта тема, что же все-таки понимается под чистым и понятным кодом
Это и в книгах порой не раскрывается, что уж говорить про статью.
khim
23.01.2018 04:08Эта картика — весьма популярна, но ведь самый простой способ уменьшить эту метрику — это увеличить количество минут. Правратив, скажем, 100 строк «хитрого» кода с 10 WTF/минуту в 10'000 строк «хорошего» кода с 1 WTF/минуту… но точно ли это улучшиение, если общее количество WTF'ов после этого возрастает?
ad1Dima
23.01.2018 06:13нет однозначного ответа на этот вопрос. Иногда 10 000 строк кода с меньшей плотностью WTF бывает лучше, чем 100 строк года делающих то же самое через один сплошной WTF.
Можете просто добавить ещё одну метрику WTF/стока кода и смотреть сразу по двум.
Free_ze
23.01.2018 09:40Вряд ли количество кода для отдельного случая будет отличаться на два порядка) В 2 или 3 раза — это звучит уже намного менее драматично.
Между количеством строк и временем так же нет линейной закономерности, ведь простой код и читается легче. Даже если чтение (до полного понимания) занимает одинаковое время, то лучше это будет простой код, без неоднозначностей и прочих подводных граблей, о которых можно забыть.khim
23.01.2018 14:48Вряд ли количество кода для отдельного случая будет отличаться на два порядка) В 2 или 3 раза — это звучит уже намного менее драматично.
Скорее в 20-30 раз. Многие «правильные приёмы» транслируют 2-3 строки в 40-50 строк легко. Заведение вместо одной простой функйии абстрактного класса, его потомка тому подобного (что внедрение зависимости может требовать), фабрики и прочее.
У меня было пара достаточно чистых экспериментов, когда мой «сложный код» переписывали и когда, наоборот, я переписывал тормозящий «тупой», но «гибкий» код. Разница была где-то между 10x и 100x, а не в «в 2 или 3 раза — это звучит уже намного менее драматично».
Даже если чтение (до полного понимания) занимает одинаковое время, то лучше это будет простой код, без неоднозначностей и прочих подводных граблей, о которых можно забыть.
Это — тоже фикция. «Простой» и «гибкий» код я перписывал именно потому, что добавление новых фич занимало слишком много времени. Так как там было порядка 100'000 строк кода, то «до полного понимания» дело не доходило — в нём просто было слишком много всего, чтобы можно было его полностью понять и все ньюансы запомнить. У его замены примерно на 5'000 кода была другая проблема — без полного понимания того, как разные части взаимодействуют между собой там было невозможно поправить фактически ничего. Но натурный эксперимент показал, что за неделю человек в этом разбирается и может менять то, что нужно менять добавляя 2-3 строки тут и там.
Такие дела.
Впрочем есть одно но: если размер компонента таков, что его при любом написании нельзя будет полностью уместить в одной голове — тогда, наверное, «тупой код» лучше.Free_ze
23.01.2018 15:09Гибкость — это уже другая характеристика, которая напрямую к сабжу не относится. Но даже в этом контексте, обобщенные алгоритмы по определению призваны сократить объем кода.
«Простой» и «гибкий» код я перписывал именно потому, что добавление новых фич занимало слишком много времени
в нём просто было слишком много всего, чтобы можно было его полностью понять и все ньюансы запомнить
Оксюморон. Тогда это было что угодно, но не гибкий код, очевидно.
Хороший код позволяет оперировать абстракциями, без необходимости полностью понимать и запоминать все нюансы реализации.
reci
22.01.2018 14:15Тупой и простой — довольно разные вещи, кмк. Поменяйте в заголовке «тупой» на «простой» — и он уже не провокационный, а очевидный, можно даже не открывать статью.
Mendel
22.01.2018 14:26Он кажется очевидным только до тех пор пока не начинаешь жалеть что у тебя нет справки из дурки и ты не знаешь где живут все эти гады для которых оно не очевидно.
Psionic
22.01.2018 14:49+1Как давно я не видел на хабре кодоснобских постов.
А как хотелось бы чтоб они вообще не появлялись.
-Почему у тебя код такой не гибкий — вдруг понадобится Х?
Через три дня к другой задачи
-Почему у тебя в коде абстрации?
-вдруг понадобится Х
-А МЫ НЕ ПИШЕМ КОД ПО ПРИНЦИПУ: ВДРУГ ВОЙНА, А Я УСТАВШИЙ, УБЕРИ РАЗ БЕЗ НИХ СЕЙЧАС МОЖНО ОБОЙТИСЬ.Ogra
22.01.2018 15:01Добавить гибкость там, где надо, но не добавлять её там где не надо — это бесценный скилл. Тут как с branch prediction — хоть полностью исключить ошибку нельзя, но уменьшить её очень даже реально.
sydorenko-vd
22.01.2018 14:54+1Как-то скопипастил код в проект со stackoverflow, а лид говорит:
зачем скопипастил? Я же вижу, не твой почерк.
SergeyGalanin Автор
22.01.2018 15:13Отвечу сразу на несколько каментов выше.
В оригинале было «Dumb Code». Причём, было не у автора, а у Гетца. «Простой», на мой взгляд, здесь не подходит по смыслу. Возможно, было бы лучше «код для тупых» или «код как для тупых» — что-то в этом духе.
И ещё не подходит потому, что «Write Dumb Code» — это, по сути, фраза-провокация, вброс. И сам блог-пост Скотта — это тоже по большей части провокация. В нём нет ничего нового, и никакой темы в нём не раскрывается. Всю суть текста можно свести к одной фразе:
«Код — это общение между людьми и инструкции для компьютера, но значительно больше первое, чем второе»,
которая, фактически, является перефразированной цитатой Кента Бека. Цель поста — в очередной раз привлечь внимание к злободневной проблеме. И попытка удалась, что тут скажешь. Скотт — красафчег!
А если нужна информация по теме простого и понятного кода, то есть хорошие книжки. Только они длинные.zif
22.01.2018 16:44И какие, по вашему мнению, книги по этой теме хорошие?
SergeyGalanin Автор
22.01.2018 16:47+1Мне «Чистый код» Дядюшки Боба нравится, например. Как раз сейчас у меня на рабочем столе лежит.
blackfox_temiks_st
23.01.2018 14:18Это случайно не тот, который советует именовать интерфейсы без I?.. Могу ошибаться.
SergeyGalanin Автор
23.01.2018 14:21Он самый. Глава 2 «Содержательные имена», пункт «Интерфейсы и реализации».
Neikist
23.01.2018 14:29+1Черт, серьезно? Когда читал внимания не обратил, пусть даже в используемой мной платформе нет ООП, но странно что я это пропустил. А там нет уточнения что это для случаев когда интерфейс — это элемент языка, или что то в таком духе (к сожалению книга не под рукой сейчас)? Просто этот пункт странным кажется.
VolCh
23.01.2018 16:56А зачем ставить букву I? Я в курсе про венгерскую нотацию, если что. Но когда при чтении кода нам жизненно важно знать, что это интерфейс, а не класс (может быть абстрактный без одного конкретного метода и свойства)?
Neikist
23.01.2018 20:46Жизненно важно — наверно нечасто, к сожалению я теоретик, за пределы хеллоу ворлдов не выходил, так что примеров как за, так и против не привел бы, но выглядит это комфортнее как то для восприятия. По крайней мере если у нас нет другого явного способа выделить при чтении интерфейсы отдельно.
VolCh
23.01.2018 22:00Так я не пойму, зачем их при чтении выделять вообще. Ладно при написании, заметить что написал new ICountable на секунду раньше, чем об этом сообщит IDE. Но читающему-то какая разница?
Free_ze
24.01.2018 00:31А зачем вообще их выделять при чтении?
DistortNeo
24.01.2018 00:42Бывает полезно при чтении API: некоторые функции на вход принимают абстрактные классы, а не интерфейсы.
Free_ze
24.01.2018 00:49И какая разница для юзера? Тому, и другому нужна реализация.
DistortNeo
24.01.2018 01:29Большая. Первый случай накладывает ограничение на базовый класс.
VolCh
24.01.2018 09:14Не очень понял, что имеется в виду. То, что читателю намекается на то, что он свою иерархию построить не сможет в языках без множественного наследия? Как часто это бывает полезно? Особенно учитывая наличие принципа инверсии зависимости.
Free_ze
24.01.2018 09:36Это искусственное ограничение лишь унылых языков, в которых запрещено множественное наследование. Для понимания алгоритма это не играет никакой роли.
sumanai
24.01.2018 15:36А вы не думали, что множественное наследование запрещают специально, как раз чтобы не было запутанного кода с неочевидным поведением?
Free_ze
24.01.2018 15:47Ну так пишите очевидный код, чтобы он не был запутанным, в чем беда?)
Конкретно это решение было не из самых удачных: в C# и Java появляются костыли для обхода этого органичения — внедрение реализации в интерфейсы теми или иными путями.sumanai
24.01.2018 16:32Ну так пишите очевидный код, чтобы он не был запутанным, в чем беда?)
Я то пишу. Но я не один во Вселенной, и есть куча народа, у которых само наличие возможности написать криво вызывает незримый зуд это сделать.
Конкретно это решение было не из самых удачных: в C# и Java появляются костыли для обхода этого органичения — внедрение реализации в интерфейсы теми или иными путями.
А в PHP есть множественное наследование интерфейсов и примеси для реализации, поэтому проблема не в запрете множественного наследия, а в отсутствии более простой и безопасной альтернативы.Free_ze
24.01.2018 16:47Я то пишу. Но я не один во Вселенной, и есть куча народа
Ничего против этого не имею. Но ограничение на наследование от этого менее унылым не становится.
А в PHP есть… примеси
«A Trait is intended to reduce some limitations of single inheritance by enabling a developer to reuse sets of methods freely in several independent classes living in different class hierarchies.»
Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.sumanai
24.01.2018 16:53+1Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.
Не вижу ничего костыльного в подходе убирания ногоотстреливательной фичи и замене её на более прозрачную реализацию. Так можно и исключения костылём назвать, есть же вычисляемые goto.Free_ze
24.01.2018 17:07-1Введение новых конструкций в язык для того, чтобы исправлять достаточно редкий кейс не особо-то повышает прозрачность.
VolCh
25.01.2018 09:47Не чтобы исправлять, а сделать более удобным в использовании, прежде всего чтобы меньше писать одинакового кода в классах, реализующих тот или иной интерфейс. Множественное наследование решило бы эту проблему тоже, но оно порождает другие проблемы типа сложных алгоритмов разрешения при ромбовидном наследовании.
Free_ze
25.01.2018 11:36Выглядит так, будто вы меня в чем-то поправили)
Дизайнеры языка редкую сложную проблему сделали насущной (ох, как же бестолковые интервьюеры любят мучить новичков-кандидатов этой разницей интерфейсов и абстрактных классов), а элегантную замену функциональности так и не придумали: в C# — внезапно в пространстве имен появляется какой-то сторонний класс, который докидывает функциональности другой сущности. В PHP былоextends
иimplements
, а теперь еще и какое-тоuse
, с похожей, но различной семантикой. Это ли красиво и понятно?
DistortNeo
24.01.2018 19:35Это такой же отвратительный костыль, как методы расширения в C# или дефолтные реализации методов в Java.
Методы-расширения — это следствие отсутствия в этих языках шаблонов времени компиляции. Если я объявляю дженерик класс, а затем хочу определить метод только для конкретных типов, то приходится писать расширение.
DistortNeo
24.01.2018 19:32Я тоже раньше считал это недостатком, когда писал на С++. Сейчас уже не считаю, потому что ни разу не столкнулся со случаем, когда множественное наследование было бы полезно и не усложняло бы код.
Один базовый класс + множественное наследование интерфейсов имеет преимущество в виде нулевого оверхеда по памяти. Вы можете навесить хоть 10 интерфейсов — занимаемый объектом объём памяти не увеличится. А вот в C++ будет по +24 байта на каждый абстрактный класс без полей из-за необходимости использования виртуального наследования. [*]
Плюс интерфейсы в C# больше похожи на концепты, чем на классы. У них есть маленькая, но очень приятная фишка — возможность реализации интерфейса базовым классом:
class Base { public void Foo() {} } interface IFoo { void Foo(); } class Derived: Base, IFoo { }
В C++ так сделать, увы, не получится.
[*] На самом деле, C++ Builder умеет оптимизировать такие вещи и не плодить vtable без нужды. Но вот gcc/MSVC/clang так не делают.Chaos_Optima
24.01.2018 20:00А вот в C++ будет по +24 байта на каждый абстрактный класс без полей из-за необходимости использования виртуального наследования.
Не будет. Реализация полиморфизма вообще не регулируется стандартом, обычно в классе пишется просто указатель на vtable и всё. Исключением может являться например ромбовидное наследование.
Пруф.DistortNeo
24.01.2018 20:41У вас пример кривой — вы вообще динамический полиморфизм не используете. Вот правильный: cpp.sh/93fd7
Почему наследование интерфейсом должно быть виртуальным, надеюсь, не нужно пояснять?
А вот тут изначальное моё исследование: habrahabr.ru/post/304492/#comment_9684026
Да, ошибся. Но +8 байт на 1 подобный «интерфейс» — все равно много.Chaos_Optima
25.01.2018 13:35Почему наследование интерфейсом должно быть виртуальным, надеюсь, не нужно пояснять?
эм… нужно. Я понимаю если бы базовый класс был бы с данными, да тогда там нужно виртуальное наследование, но вот для интерфейса совсем непонятно, уменьшить количество вызовов деструкторов интерфейса? Они и так практически всегда без реализации. Виртуальное наследование нужно исключительно в ромбовидных схемах, когда общий предок имеет данные а не просто когда он интерфейс. Бездумно использовать виртуалку так себе решение.
DistortNeo
24.01.2018 00:40Вообще говоря, причин подобных правил именования идентификаторов, как я понимаю, две: недопущение конфликтов имён и повышение эффективности читаемости/писаемости кода.
Последний аргумент уже не так актуален с развитием IDE и современных языков и методологий программирования. Поэтому я до сих пор не понимаю людей, которые ставят префиксы/суффиксы для полей-членов классов — это же дико неудобно и нисколько не повышает читаемость.
IDE может выделять разными цветами классы, структуры, интерфейсы, а также различать локальные, статические переменные, аргументы и т.д. И если первым функционалом я активно пользуюсь (очень удобно в C# сразу видеть, что тип является структурой), то вторым — нет, выделение переменных разным цветом — это информационный шум.
А вот конфликты имён никуда не делись. В том же C/C++ есть макросы, которые нехило всё засирают.
Что касается интерфейсов: лично я всегда пишу название с буквы I. Потому что я сначала придумываю интерфейс (что я хочу получить), а уже потом думаю, как буду его реализовывать. Да и вообще полезно иметь много мелких интерфейсов — SOLID, всё такое. И иногда получается, что имя класса, реализующего интерфейс, совпадает с именем интерфейса, но без I. Потому я и называю интерфейсы всегда с I.blackfox_temiks_st
24.01.2018 08:43несомненно, именование приватных полей типа "_field" вызывает странные ощущения и легкое раздражение.
Что касается интерфейсов, то да, имя класса может совпадать с именем интерфейса, так что начинать именовать интерфейсы всегда стоит с I.
Так же при такой записи, сразу понятно что ILabelDetetor — это интерфейс.
switch(labelType) { case LabelType.EveryDay: ILabelDetector = new RedLabelDetector(); break; case LabelType.White: ILabelDetector = new WhiteLabelDetector(); break; case LabelType.Special: ILabelDetector = new SpecialLabelDetector(); break; case LabelType.FlyBuy: ILabelDetector = new FlyBuyLabelDetector(); break; case LabelType.New: ILabelDetector = new NewLabelDetector(); break; default: return null; } return ILabelDetector.DetectLabel(result, bitmap, googleORM);
PS: я новичок в программирование (1 год практики в компании), так что код приведенный выше может казаться неправильным и непонятным.lair
24.01.2018 09:40+1Ну этот код и нарушает как минимум два принятых нынче правила именования: (а) не надо именовать переменную по ее типу и (б) локальные переменные именуются с маленькой буквы.
А все потому, что, на самом деле, низачем не надо знать, что локальная переменная
labelDetector
имеет тип интерфейса.blackfox_temiks_st
24.01.2018 11:12Уточню немного.
(a) вы про bitmap? если нет, то прошу указать на место.
(b) ILabelDetector? в других местах вроде все ок.lair
24.01.2018 11:29Нет, я про
ILabelDetector = new RedLabelDetector();
. Эта строчка читается как "давайте присвоим новый экземпляр интерфейсу (не переменной)".
VolCh
24.01.2018 10:01+1Чем тут ухудшиться читаемость, если заменить на labelDetector? Как по мне так только улучшится. И вообще пример на переменную, а не на имя класса/интерфейса.
Free_ze
24.01.2018 11:38Хотели как лучше, а получилась венгерская нотация) Будь здесь
LabelDetector
базовым классом классом, то ничего бы не изменилось бы.
VolCh
24.01.2018 09:18Если где-то очень важно избежать конфликта имён или явно показать, что это интерфейс, то лучше добавить суффикс Interface, по-моему, чем префикс I. Хотя бы потому, что читатели кода могут и не знать, что вы используете I для интерфейса, особенно если это явно не указано в ваших стайлгайдах.
Deosis
24.01.2018 12:0599% .Net использует префикс I для интерфейса, поэтому не стоит усложнять жизнь себе или другим, отступая от такого соглашения.
Free_ze
24.01.2018 12:0799% процентов джавистов не используют префикс «I» и живут же как-то!
ad1Dima
24.01.2018 12:15Придумывая как составить прилагательное или обобщить существительное.
Free_ze
24.01.2018 12:21Просто не ставят префикс.
ad1Dima
24.01.2018 12:23+1И ставят суффикс Impl у реализации, ага. (будто это лучше)
Free_ze
24.01.2018 12:27Неужто реализация настолько абстрактна, что выделить в имени типа ее особенности никак нельзя?
Кроме того, интерфейсы обычно лежат в отдельном пакете, поэтому конфликта имён не будет.
VolCh
24.01.2018 13:31Создавать интерфейсы на каждый класс часто ничуть не даёт ожидаемой по прочтению какой-нибудь статьи по DIP выгоды, при этом нарушая KISS и YAGNI. А если у вас в голове появилась мысль, что нужно два класса с общим интерфейсом, то скорее всего у вас в голове появится и мысль чем они будут отличаться и это отличие имеет смысл продемонстрировать в имени, даже если пока у вас второй реализации нет.
В любом случае суффикс Interface или Impl более говорящий чем префикс I.
ad1Dima
24.01.2018 13:40В любом случае суффикс Interface или Impl более говорящий чем префикс I
С учетом, что префик I, является общепринятым обозначением — никакой разницы.
А о чем говорит Impl в абстрактном классе SocketImpl мне не очень понятно.
В общем, мой изначальный коммент обращал внимание лишь на то, что отказ от префикса I не решает никаких проблем и в основном дело вкуса.VolCh
24.01.2018 14:08С учетом, что префик I, является общепринятым обозначением — никакой разницы.
Судя по треду, он является общепринятым лишь в некоторых экосистемах типа .NET и C++, а в других таковым не является.
Отказ от префикса решает некоторые проблемы и создаёт новые. Введение префикса также решает некоторые проблемы и создаёт новые. Судя по всему, в обоих случаях решенные и создаваемые проблемы друг друга нивелируют. Хотя, может, в одном из случаев уже есть понимание, что создаваемых проблем больше, но груз обратной совместимости держит. Может пока держит.
ad1Dima
24.01.2018 14:14+1Я о том и говорю, в общем-то. Поработав с обоими подходами я считаю что это все вкусовщина и делать нужно так, как принято в платформе.
Просто это мне создавало некоторые трудности, когда я пытался спроектировать библиотеку с общим программным интерфейсом (на уровне соглашения) для нескольких платформ. Для каждой из платформ на своем языке.
Ну, то есть, чтоб взяв пример работы с библиотекой на одной платформе, можно было очевидным образом превратить его в пример для другой платформы.
DistortNeo
24.01.2018 19:41А о чем говорит Impl в абстрактном классе SocketImpl мне не очень понятно.
Ага. Я бы назвал SocketBase
sumanai
24.01.2018 15:40Конечно следовать лучшим практикам конкретного языка нужно. Но это принято не во всех языках, например, в PHP используют суффикс Interface.
DistortNeo
24.01.2018 19:40В .NET принят префикс, в Java — суффикс. Факт в том, что он есть, и не надо из этого устраивать холивар.
В защиту префикса скажу, что слово "Interface" может использоваться при программировании UI. А вот две заглавные согласные буквы в начале имени интерфейса — это уже более редкий случай.
Chaos_Optima
24.01.2018 12:19Поэтому я до сих пор не понимаю людей, которые ставят префиксы/суффиксы для полей-членов классов — это же дико неудобно и нисколько не повышает читаемость.
Тут несколько причин, не всё ограничивается только IDE. Даже с IDE для понимания это удобнее, когда читаешь m_ сразу понимаешь что это поле класса. У разных разработчиков подсветка может быть настроена по разному. Ну и что по моему мнению наиболее удобно так это то что при наборе m_ подсказчик подсказывает именно поля а не всё подряд. Тоже самое с интерфейсами, набираешь I и тебе показываются только интерфейсы.DistortNeo
24.01.2018 19:47Даже с IDE для понимания это удобнее, когда читаешь m_ сразу понимаешь что это поле класса.
Если для того, чтобы понять, что переменная является членом класса, а не локальной переменной или аргументом, нужно эту переменную выделять префиксом, то это признак дурно пахнущего кода.
Да и просто набор лишних символов, не относящихся к бизнес-логике, раздражает.
У разных разработчиков подсветка может быть настроена по разному.
Так дайте разработчику выбор. Лично я такие вещи не подсвечиваю, потому что не вижу в этом смысла. Кому надо — сами настроят IDE.
при наборе m_ подсказчик подсказывает именно поля а не всё подряд
Ага, и поэтому в Google C++ coding style guide для членов класса вообще предлагается использовать суффикс (_).
Ogra
24.01.2018 09:29+1А зачем ставить букву I?
Ну, для С++ я вижу смысл — там нет такой языковой конструкции, как интерфейс. Отделить интерфейсы от классов через стайлгайд — нормальная идея.
Для языков, где интерфейсы есть — бесполезная буква.
SergeyGalanin Автор
24.01.2018 10:06Поддерживаю предыдущих докладчиков. Боб очень щепетильно относится к именованию, но с интерфейсами он малость перегнул. Правда, в том абзаце про имена интерфейсов он выражает мысль про нелюбовь к префиксам в такой форме, что становится ясно, что это его личное предпочтение, тем самым оставляя читателям право сделать собственный выбор.
Есть, несомненно, много неприкосновенных правил, которые нельзя нарушать в программировании, но использование префиксов для интерфейсов к ним не относится. Тут, я считаю, следует руководствоваться только личным удобством (и удобством коллег, конечно же).
Legion21
22.01.2018 16:10+1Хм… сплошная критика… ради чего?) Пишите код как хотите…
Mendel
22.01.2018 16:24Да, да, пишите как хотите, только адрес свой указать не забудьте. Бензопила у меня есть, справку купим…
Psionic
23.01.2018 16:59Вот возьму и в сейфе дома дробовик заведу, от нерадивых которые то того чтоб молча
«молча курить» чужой код (как это делаю я) начинают кого-то винить — твой таск, твое дело со всеми вытекающими.Mendel
24.01.2018 13:26Обычно это проходит.
Достаточно поработать пару лет над проектом с парой дюжин разработчиков, когда в сфере твоей ответственности работает команда из пяти человек, а с остальными коммуникации редкие… Сразу понимаешь что дробовик купил не зря. Только с целью ошибся...)Psionic
24.01.2018 14:27Что, извините, проходит? Я всегда стою на позиции, твой таск — твое дело, и воспринимаю кодокопание в чертогах чужих мыслей как должное, ну не может быть такого, как хотят манагеры (когда об этом зашла речь — один мне сказал что к такому надо стремится) чтоб у всех мозги были выровнены по одним и тем-же лекалам. Да и вообще от одинаковости мозги закостенеют, а я такого не хочу.
FlameArt
22.01.2018 16:44Это 4 разных проблемы:
1. Люди стараются выглядеть умнее, чем они есть на самом деле. И код тут не причём, это лишь желание уважения (либо подросток до 25), которое можно создать здоровой атмосферой в команде.
2. Люди хотят развиваться и пользоваться инструментами, о крутости которых слышали от других. Люди хотят разрабатывать свои фичи. И это нормально, можно лишь создать культуру такого развития, чтобы проекты не превращались в хаос, не затягивались, а новые идеи были документированы и обсуждены.
3. Люди не понимают к чему приведут их решения с архитектурной точки зрения. Из-за недостатка опыта, который не так страшен, намного страшнее когда не понимают как их кусочек будет лежать в общей архитектуре, для чего они его делают и как в целом тут всё работает. Такое я наблюдал регулярно: не понимающий телеги прогер будет делать колесо с набором грязи, которую он считает правильной.
4. Для долгосрочного развития проекта надо писать понятный код в единой канве. И это тоже не задача кодеров, культуру этого (и инструменты) должны выстраивать тимлиды и управленцы, вплоть до первого лица.nporaMep
23.01.2018 11:01+2По пункту 3 очень часто замечал, что архитекторы наархитекторили чего-то без описания и потом не могут сами объяснить как же вкрячить вот это то что надо для этой задачи.
Или архитекторы просто сваливают, не оставив описания какую ж архитектуру они тут нагородили.
Или архитекторы архитектят начитавшись чего-нибудь и решивших применить последнее прочитанное на вот этом проекте сами не до конца понимая всех узких мест.
emacsway
22.01.2018 16:44Понять эту статью можно только с позиции опыта. Могу ее добавить цитаты известных авторитетов на тему простоты и проблемы умных людей. Еще по теме статья М.Фаулера Is Design Dead?.
encore-show
22.01.2018 16:44Согласен с тем что, простой код понятнее, особенно когда проект командный. Но ведь некоторые к этому относятся как к искусству. Во времена ограниченных ресурсов люди придумывали новые обходы и как оптимизировать, а сейчас «Если код отрабатывает долго, то, давайте увеличим железо». Считаю что должен быть баланс)
MaximM2017
22.01.2018 16:44-1В комментариях часто проскакивает слово «простой». Может можно заменить определение «тупой» на «простой»?
artemmityushov
22.01.2018 16:45Был у меня один коллега который придерживался двух принципов:
1. Если есть функцию то ее нужно использовать не думая нужна она или нет нужно придумать место ее использования.
2. Если у языка есть возможность то ее нужно обязательно использовать.
В результате все его проекты были как учебник по языку, использовалось все, и он этим безумно гордился, только вот никак не мог понять почему его каждые пару лет просили покинуть компании т.к. на любые доработки его старого кода нужно были минимум в 3-4 раза больше ресурсов чем у всех остальных разработчиков.
yurec_bond
22.01.2018 17:15+1«Любой дурак может писать код, понятный компьютеру. Хорошие программисты пишут код, понятный людям.»
Совершенно согласен с этой фразой и в связи с этим не понимаю как JavaScript приобрел такую популярность?
Из-за отсутствия типизации совершенно не возможно понять кто кого вызывает и как код работает.
И не смотря на несильно удачные попытки создать IDE которое поможет навегировать и рефакторить код, самым лучшим инструментом остается Search/Search-ReplaceVolCh
22.01.2018 17:44+1Ну так заказчику надо, чтобы работало, чтобы кто-то его хотелки перевёл в доступный компьютеру язык. И альтернативы последние лет 10 в клиентской веб-разработке нет.
AlexPu
22.01.2018 17:34>>я ненавижу термин «сениор» и называю себя просто разработчиком ПО
Фигасе… а думал я один такой… в свои 48 лет при стаже в отрасли более 25-ти лет (и именно разработчиком), я могу себе позволить не считаться с титулами…
>>«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».
Опять-же я думал, что это я придумал лет 15 назад наверное (но я никогда в такого никому не говорил. Ну… может по пьяне сболтнул кому?)
Nondv
22.01.2018 20:47Милый пост.
Спасибо за перевод!
P.S. и добро пожаловать на хабрахабр;D (песочница)
aPiks
22.01.2018 21:54«Всегда кодируй так, будто парень, который будет поддерживать твой код — необузданный психопат, и он знает, где ты живёшь».
Это выражение Джона Вудса, сказанное им в 1991 году на конференции.
С остальным в статье согласен — чем проще код, тем лучше.
no1s1a
22.01.2018 21:54Чисто на ментальном уровне мне нравиться эта мысль. Простой и понятный код — это произведение искусства. Иногда читаешь код, вроде понятно как это работает, а вроде ??????????
marf1k
22.01.2018 21:54У нас практика — не писать комменты, только документацию.
Почти не встречаются случаи — отковенная муть но я откомментировал и моя совесть чиста
ap_gubarev
25.01.2018 12:17Опасная фраза «тупой код». Тупые воспримут ее буквально и будут писать лапшеговнокод
devalone
Достаточно тупой код чтоб быть синьёром?
VioletGiraffe
Нет, недостаточно. Можно ещё тупее:
if ((symbol >= 'a' && symbol <= 'z') || (symbol >= 'A' && symbol <= 'Z'))
Речь не о том, что лучше или хуже, но так же реально тупее, если применять это слово к коду, а не к его автору :)
Jenkinse
Почему этот кусок кода тупой?
Finesse
Решает задачу наиболее простым образом
ozonar
В принципе можно ещё подумать и найти ещё более тупой способ.
VolCh
if (is_alpha(symbol))
laphroaig
а если symbol=='Ж'? В первом случае такого вопроса просто не возникает, здесь же придется смотреть контекст
iig
А если кодировка отличная от 7-битной? И язык, например, казахский? Не нужно строить лисапедов без необходимости.
MarkGrin
Стандарт с++ не гарантирует порядок символов английского алфавита в char. Поэтому, чтобы проверить является ли символ буквой нужно пользоваться стандартной функцией std::isalpha.
Asikk
Потому как легко пропустить одну букву, и функция будет вести себя не так, как ей положено.
Deosis
Вот только для русской кодировки такой однострочник не работал и пропускал букву Ё.
BasilSnowman
и не лень было набирать
ZyXI
<C-r>=map(range(char2nr('a'), char2nr('z')) + range(char2nr('A'), char2nr('Z')), 'printf("case''%s'':return 1;break;", nr2char(v:val))')<CR>
zagayevskiy
и не лень было набирать
sim31r
Возможно это вывод в цикле в текстовый файл, программа из трех строчек… а уже написали ))
devalone
Лень — плохое качество для программиста, говорю тебе как синьёр.
Mikhail_dev
Пфф. Лень — двигатель прогресса. Программисту лень что-то делать, и он это дело оптимизирует.
devalone
Не спорь с синьёром!
alexeykuzmin0
DEmon_CDXLIV
habrahabr.ru/post/275841
Цитата №8.
areht
Ну какой синьёр, вы букву пропустили
Ostrouschcko
Теперь я думаю почему индусы не сениоры
mobi
Возможно, когда-то давно, во времена EBCDIC и других альтернативных (не ASCII) кодировок, это и был тот самый «тупой», зато платформо-независимый код.
AR1ES
Как показывает моя практика, времена EBCDIC еще не прошли =)
KoCMoHaBT61
Не тянешь на синьора. Тупизна кода достаточная, но функция должна возвращать bool, а ты возвращаешь int.
fatronix
В C до C99 не было bool.
mobi
Тем не менее, в этом случае объявление функции подразумевает, что где-то выше по коду было
что в свою очередь, как правило, сопровождаетсяПоэтому было бы «элегантно» и возвращать
true
илиfalse
.mobi
Пришла в голову мысль, что
bool
может быть объявлен черезtypedef
, а использовать#define
запрещено coding style, используемым в компании. Но и тогда, наверное, можно было быtrue
иfalse
черезenum
определить.ZyXI
Вообще?то
bool
(как иtrue
иfalse
) по стандарту C99 именно макросы, раскрываемые как_Bool
(1
и0
). Стандарт даже разрешает программе переопределять их (правда, сразу объявляет такую возможность устаревшей). Но#define bool int
— это что?то сомнительное,sizeof(_Bool)
обычно единица, что означает, что вы не сможете с таким определением приниматьbool *
из библиотек на C99. А при другом соглашении о вызовах или порядке байт и простоbool
передавать и принимать также не сможете.devalone
Вообще-то bool, посмотри внимательней
khim
Это как раз то, о чём говорится в статье. Да, 0 и 1 будет преобразованы в правильный bool — но это как раз шажок на пути к я использую локальный потоковый синхронный конструктор копирования JavaBean с интерфейсом по-умолчанию и непроверяемыми исключениями кастомных дженериков вместе с многофункциональным генератором кода JAXB Lombok с усиленной безопасностью
otvorot2008
Под такой тип кода есть даже специальный термин: "китайский код" :)
alexeykuzmin0
Дело не в тупости, а в читаемости. И ваш код для чтения неудобен, по той простой причине, что не умещается на экран.
devalone
Не проблема
alexeykuzmin0
Так он в экран уместился, но удобнее для чтения не стал.
vkflare
Разбор аргументов в gradlew
khim
Ну это ж shell! Какую вы предлагаете альтернативу без bash'измов?
TigerClaw
Вы не поверите, но встречал подобный код в продакшене. Там перебором соотносились номера в очень большой гостинице и их порядок. Причем делалось это в нескольких местах.
AlexKslv
Почему мне нравится Питон:
import string
symbol = 'a'
if symbol in string.ascii_letters:
print(«I'm a letter!»)