Code style, использовать ли переменные?

kruzer25

Ср.

static SomeType DoSomething(SomeOtherType arg) {
var x = arg.something;
var y = x.DoSomething;
var z = new SomeClass(y);
var u = z.something;
return u;
}

vs
static SomeType DoSomething(SomeOtherType arg) {
return new SomeClass(arg.something.DoSomething.something;
}

Какой вариант лучше (конечно, при условии, что все временные переменные используются по одному разу)?
Во втором варианте и дебажить можно:
static T PassDebug<T>(this T obj) {
Debugger.Debug(obj);
return obj;
}

static SomeType DoSomething(SomeOtherType arg) {
return new SomeClass(arg.something.PassDebug.DoSomething.something;
}

и, в принципе, даже допускается совершение каких-то промежуточных действий с объектами (хотя и выходит говнокод):

static T Tee<T>(this T obj, Action<T> todo) {
todo(obj);
return obj;
}

static T PassDebug<T>(this T obj) {
return obj.Tee(Debugger.Debug);
}

static SomeType DoSomething(SomeOtherType arg) {
return new SomeClass(arg.something.PassDebug.DoSomething.Tee(obj => obj.UpdateSomething.something;
}

И ещё пришло в голову - почему на уровне языка (C#) нет фичи, чтобы методы объекта являлись и методами класса, принимающими на вход объект? То есть, чтобы

class SomeType {
void SomeMethod(SomeOtherType arg) { }
}

работало, как

class SomeType {
void SomeMethod(SomeOtherType arg) { }
static void SomeMethod(SomeType obj, SomeOtherType arg) { return obj.SomeMethod(arg); }
}

?
Тогда можно было бы вместо всяких SomeFunc(x => x.DoSomething писать просто SomeFunc(TypeOfX.DoSomething)
Хотя, выходит, даже лучше, чтобы статический метод выглядел как-то так:

class SomeType {
void SomeMethod(SomeOtherType arg) { }
static Action<SomeType> SomeMethod(SomeOtherType arg) { return obj => obj.SomeMethod(arg); }
}

и можно будет писать вместо SomeFunc(x => x.DoSomething('xxx' писать SomeFunc(TypeOfX.DoSomething('xxx'

sergeikozyr

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

kruzer25

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

6yrop

TypeOfX

чё за херь?

kruzer25

Не бойся, я не имел в виду макросы. Просто имя класса, такое же, как SomeClass.

6yrop

тогда, на мой взгляд, вот это SomeFunc(x => x.DoSomething лучше. В этой записи не упоминается имя класса, оно выводится автоматически. Я все больше и больше проникаюсь какая крутая вещь вывод типа. :)

6yrop

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

6yrop

static SomeType DoSomething(SomeOtherType arg) {
return new SomeClass(arg.something.PassDebug.DoSomething.Tee(obj => obj.UpdateSomething.something;
}
да, это ты увлекся, это уже ковнокод получаетca — вводится лишняя сущность — метод Tee.

kruzer25

тогда, на мой взгляд, вот это SomeFunc(x => x.DoSomething лучше. В этой записи не упоминается имя класса, оно выводится автоматически.
Это - плюс. А вот все бесконечные x => x.*** - имхо, минус.

kruzer25

да, первый вариант предпочтительнее. Второй вариант считаю плохим стилем
Можно поконкретнее?

6yrop

x => x.*** - имхо, минус.
а что тут лишнего? это "x => x."? да, возможно, и можно было бы сократить до ".", но, имхо, это не особо принципиально, привыкаешь. Когда лишними являются четко повторяющиеся конструкции, то к такому привыкаешь и глаз уже пропускает такое. А вот, если нужно писать и читать имя класса это уже действительно лишняя инфа и поэтому напрягает.

kruzer25

О, я понял, чего мне не хватает.
Чтобы к TResult f(T1 arg1, T2 arg2, T3 arg3) можно было обратиться и как к Func<T1, TResult> f(T2 arg2, T3 arg3 и как к Func<T1, T2, TResult> f(T3 arg3) (Func<T1, T2, T3, TResult> f уже есть, это просто f без скобок); ну и то соответствие между динамическими и статическими методами, о котором я уже сказал (т.е. чтобы можно было в любом классе (необязательно статическом) можно было написать статический extension для данного класса метод).
Первое решается и руками, но это задолбаешься для каждого количества аргументов писать по 2*n extension-методов curry (для func и для action)... да и явно типизированные к своим типам (ну вроде как Predicat<T> вместо Func<T, bool>) делегаты придётся явно кастовать в Func-и и Action-ы...
Когда лишними являются четко повторяющиеся конструкции
Ну так они не чётко повторяются, ты не можешь написать x => x => x.DoSomething(x надо писать x => y => y.DoSomething(x).

karkar

О, я понял, чего мне не хватает.
Смени язык на тот, что поддерживает карринг. А также где можно определять control flow операторы типа |> и т.п. F# тот же.

lubanj

имхо надо чтобы весь код нормально читался либо сверху вниз, либо слева направо.
поэтому такая ботва
return new SomeClass(arg.something.DoSomething.something;

идет лесом
а такую
 var x = arg.something;
var y = x.DoSomething;
var z = new SomeClass(y);
var u = z.something;
return u;

можно свернуть до такой
var y = arg.something.DoSomething;
var z = new SomeClass(y);
return z.something;

6yrop

Ну так они не чётко повторяются, ты не можешь написать x => x => x.DoSomething(x надо писать x => y => y.DoSomething(x).
да, автоматический карринг не помешал бы. Может и введут когда-нибудь.

6yrop

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

lubanj

ну как бы к объекту arg.something.DoSomething применяется сначала приведение (операция стоит от него слева а потом взятие поля - операция уже стоит справа от него

6yrop

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

new SomeClass(
arg.something.DoSomething
).something;

6yrop

т.е.выражения вида f(g(h(x принципиально плохо читаются?

kruzer25

Я бы не сказал, что они читаются сильно лучше, чем x.h.g.f

lubanj

 f(g(h(x
как раз нормально читается слева направо
а вот если так: f(g(h(x).s).y) то плохо
апд:второй вариант пенартура в однобуквенной буквенной нотации превращается вf(a.s.d.x
может это и нормально, но мне режет глаз

6yrop

что они читаются сильно лучше, чем x.h.g.f
да, но они позволяют вводить несколько аргументов у любой функции, и при это все аргументы симметричны, а как твою (extention) запись обобщить на случай многих аргументов?

kruzer25

а как твою (extention) запись обобщить на случай многих аргументов?
В теории - { x, y }.f
Просто имеет смысл определиться, какую запись использовать - слева направо или справа налево. А сейчас приходится в части случаев писать x.f а в части - f(x).

kruzer25

а вот если так: f(g(h(x).s).y)
Ты просто идеальную иллюстрация моего примера запостил: x.h.s.g.y.f.

6yrop

{ x, y }.f
тогда уж (x, y)f
но, на самом деле, справа налево это плохо читается — сначала идет формирование пары (x, y а потом мы видим зачем мы эту пару формируем (чтобы передать в функцию f). Классическая запись f(x, y) приятнее — сначала видно, что идет вызов функции f, затем идет формирование для нее аргументов.

kruzer25

сначала идет формирование пары (x, y а потом мы видим зачем мы эту пару формируем (чтобы передать в функцию f).
Так же можно сказать и про обычное x.f
Речь не о том, что лучше, подход "слева направо" или подход "справа налево", а о том, что любой из них лучше подхода "в 50% случаев слева направо, а ещё в 50% - справа налево".

6yrop

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

kruzer25

не скажи, здесь x.f у нас есть объект и мы для объекта вызываем метод. А вот если аргумента два, то мы формируем пару и потом вызываем функцию. Так вот хочется сначала видеть, зачем мы начинаем формировать пару.
А если x заменяем на x.y?
Чем принципиально отличается x.y.f от {x, y}.f? Точно так же - "хочется сначала видеть, зачем мы начинаем вызывать метод y у x".

kruzer25

Кстати, можно, для совсем уж полной наглядности, ввести что-то вроде:
static TResult _<TResult>(this Func<TResult> func) {
return func;
}
static TResult _<T1, TResult>(this T1 arg1, Func<T1, TResult> func) {
return func(arg1)
}
...

и тогда можно будет вместо
{x, y}.f

писать
{x, y, f}._

Но это уже было в симпсонах.

6yrop

хочется сначала видеть, зачем мы начинаем вызывать метод y у x
не знаю как ты, а я вызываю метод затем, что этого требует задача, которую я программирую. А вот, потребность формировать пару возникает из-за способа записи вызова функции с двумя аргументами. Формирование пары это фактически начало записи вызова функции, и, на мой взгляд, лучше сначала прочитать название функции
Запись f(g(h(x разворачивается в цепочку без вложенных скобочек x.f.g.h только при одном аргументе. Оптимально как раз смешивать право и левосторонние стили
— правосторонний стиль позволяет избавиться от вложенные скобочки для функций с одним аргументом, что является плюсом. Но имя функции стоит не на первом месте в записи вызова функции с несколькими аргументами, что является минусом.
— для левостороннего стиля ситуация обратная.
Запись вызова функции при правостороннем стиле
— для функций одного аргумента “.f”. Видим, что запись начинается почти с имени функции. Точка не мешает, поскольку это символ, который не зависит от вызываемой функции.
— для нескольких переменных “{..., ..., ..., ... ...}.f”. Тут уже в начале идет формирование набора аргументов, а мы еще не знаем, зачем мы формируем этот набор.

kruzer25

Тут уже в начале идет формирование набора аргументов, а мы еще не знаем, зачем мы формируем этот набор.
Ещё раз. А если у нас в первом случае ("функция одного аргумента") этот "один аргумент" записывается не одной буквой, а тоже каким-то сложным выражением? Получается то же самое - формируем аргумент, а ещё не знаем, зачем.

kruzer25

Кстати, даже если временная переменная используется несколько раз:
var x = DoSomething(arg);
var y = f(x);
var z = g(x);
return h(y, z);

можно, ценой говнокода, обойтись без этой отдельной строчки:
return (x => h(f(x g(oSomething(arg;

Тут уже нихрена не понять, что происходит, но, если немного модифицировать (нынешний язык такого не позволяет то выйдет терпимо:
return arg.DoSomething.(x => h(f(x g(x;

или, после ещё одного преобразования:
return arg.DoSomething.(x => { f(x g(x) }).h;

или, совсем сильно фантазируя:
return { arg.DoSomething f, g }.Calculate.h;

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

6yrop

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

kruzer25

а вот пару формируем из-за способа записи
Чем отличается {x, y} от x.f?
В лиспе вот, вроде бы, именно такая запись используется, и особо никто не жалуется.

6yrop

Чем отличается {x, y} от x.f?
тем, что вложенные скобочки {{x, y}, z} читаются хуже, чем x.y.z

6yrop

В лиспе вот, вроде бы, именно такая запись используется, и особо никто не жалуется.
возможно, у записи {{x, y}, z} есть плюсы, которые перекрывают указанный минус. Я пока не в курсе.

6yrop

{x, y} от x.f?
кстати, ты тут опечатался или нет?

kruzer25

кстати, ты тут опечатался или нет?
Нет - имеются в виду не конкретные случаи, а вообще.

kruzer25

возможно, у записи {{x, y}, z} есть плюсы, которые перекрывают указанный минус. Я пока не в курсе.
В лиспе афаик вместо фигурных скобочек - круглые, а запятых нет. Перекрывают? :D
UPD: Вру, в лиспе функция идёт в начале, а не в конце... хотя, если подумать - всё это условности, наверное, f(x) ничем не отличается от x(f). Какая разница, sqrt(4) или 4(sqrt)?
тем, что вложенные скобочки {{x, y}, z} читаются хуже, чем x.y.z
Чем хуже?
Ну и не забывай, что в моей выдуманной нотации для функций с одним аргументом можно скобочки не писать, нужна в них возникает только при написании функций от нескольких аргументов.

Maurog

Ср.
code:
static SomeType DoSomething(SomeOtherType arg) {
var x = arg.something;
var y = x.DoSomething;
var z = new SomeClass(y);
var u = z.something;
return u;
}
vs
code:
static SomeType DoSomething(SomeOtherType arg) {
return new SomeClass(arg.something.DoSomething.something;
}
Какой вариант лучше (конечно, при условии, что все временные переменные используются по одному разу)?
В телепатическом режиме предполагаю, что лучшим не является "самый короткий код".
Лучший вариант виден в конкретике. В каких-то случаях предпочтителен один вариант, а в каких-то другой. Включив воображение, я все же в 90% случаях выбрал бы первый вариант и обязательно дал бы осмысленные имена переменным вместо x, y, z. Тогда полученный код настроен на легкую модификацию и расширение в будущем.
Во втором варианте и дебажить можно:
Так как код приходится менять, то второй вариант я бы назвал "недебугельным". Первый вариант гораздо (даже слишком) debugger-friendly.

6yrop

я бы назвал "недебугельным".
на самом деле, в 95-99% случаев под дебагом можно посмотреть столько же инфы и для второго способа записи

kruzer25

Ага. Дебажим-то обычно какой-то промежуточный результат.
var a = xxx.DoSomething;
Debugger.Debug(a);
a.DoSomething;

и
xxx.DoSomething.PassDebug.DoSomething;

aleks058

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

bool isCurrentDirectorySystem = (Enviroment.CurrentDirectory == Environment.SystemDirectory);

if (isCurrentDirectorySystem)
...

Я сейчас начинаю предпочитать второй вариант (если документировать переменными нет необходимости но немного измененный (верю и надеюсь, что он помогает дебагить - даёт доп. инфу о строке с исключением,- но не проверял).
В предельном случае будет как-то так:

static SomeType DoSomething(SomeOtherType arg)
{
return new SomeClass
(
arg
.something
.DoSomething
)
.something;
}
Оставить комментарий
Имя или ник:
Комментарий: