Тут с вами соглашусь! Пожалуй, действительно, слабый момент. Я очень долго думал над тем, как избежать неоднозначности в таких случаях, когда мы ограничиваем какое-то значение нижним или верхним пределом (здоровье не может опуститься ниже нуля).
// Реальный классический подход
string path = ...;
Path.GetFileNameWithoutExtension(path);
path.Without(x).AtEnd определён для задач, предполагающих работу не только с расширениями файлов.
var x = path.Without(".exe"); // x это что? Я ожидаю, что это строка без всех вхождений ".exe", так ли это? И чем это отличется от path.Replace(".exe", "")?
К сожалению, C# не позволяет определить path.Without(".exe") так, чтобы он имел смысл, если не было вызвано никакое из продолжений вроде End. Тем не менее, это решается с помощью path.Without(".exe").Everywhere.
Учитывая, что System.Linq.IEnumerable имеет аналогичный метод, читается как… ну, Вы поняли?
Согласен, двусмысленность есть. Но она — в примере, в демонстрации, общий принцип остаётся таким же: использовать using static, чтобы упрощать понимание и читаемость некоторых участков кода.
Приведу другой:
public enum JobStatus
{
None = 0,
Running = 1,
Completed = 2,
Cancelled = 3,
Failed = 4,
}
public class Job
{
public JobStatus Status { get; }
public bool Is(JobStatus status) => Status == status;
}
// Как вызывать метод `Is`?
// Как обычно.
job.Is(JobStatus.Running);
// С помощью `using static`.
job.Is(Running);
Разумеется, у using static есть свои нюансы (нужно, чтобы не было коллизий в именах), но, тем не менее, это инструмент!
Вы понимете разницу между default(int) и default(int?). Вы понимаете, что x.GetValueOrDefault() и x.OrDefault() это совсем не одно и то же?
Одно и то же. Вы же не пишете int? numberOrNull, а пишете int? number (что, по вашей логике, неверно, поскольку там не число, а число или ничего). Некоторые детали отдаются типу и контексту, так что тут, на мой взгляд, всё в порядке.
Более того, мне интересно, как это противоречит самой идее включать название переменной в имя метода и тем самым добиваться fluent.
Нельзя заставлять миллионы разработчиков привыкать, им нужен четкий и понятный API.
Боюсь, чёткость этой разницы крайне сомнительна, а критерий производительности устарел. Советую взглянуть на вторую часть статьи: в большинстве случаев важна семантика, а не конкретные технические детали.
Я и уточнил, что подразумевается под «это не действие»
Я подразумевал такую штуку: если нужно показать действие (Put), то мы используем глагол; если нужно показать просто какую-то вещь как часть другой — не используем.
Можете полагать значение с ключом x как бы свойством хешмапы, как, например, у яблока есть свойство вес.
Знаю, в Java всё равно пишут getWeight(), но, мне кажется, это кто-то зря придумал и очень-очень давно. Лучше weight(). Потому что вес — это не действие, которое яблоко умеет делать, а его свойство.
Рассчитывать на контекст — плохая идея. Апи должно быть читабельным даже без него.
На мой взгляд, напротив: нужно всегда помнить, что у каждого вызова есть контекст, например, методы вызываются или в контексте имени переменной, или в контексте имени класса (если метод статический). Иначе получится Directory.CreateDirectory.
Проектируя методы с опорой на контекст, например, на определённую форму слова переменной, мы тем самым ещё и заставляем разработчика избегать всяческих двусмысленностей и коверканий: One лучше подходит к users, чем к usersRepository.
Кстати, что скажете насчет File.ReadLines и File.ReadAllLines? У меня к этим методам давний вопрос, как можно было сделать апи максимально неочевидным =)
И не подозревал о существовании File.ReadLines. Да уж, выглядит, конечно, не очень (с точки зрения API). Но я бы не хотел видеть File.EnumerateAllLines, которое, уверен, первым приходит в голову как хорошее решение.
Понимая, что тут очень важно показать разницу между ленивым считыванием строчек из файла и неленивым, я бы, вероятно, написал нечто такое:
public class FileLines
{
public static FileLines Of(string path) =>
new FileLines(path);
private readonly string _path;
private FileLines(string path) =>
_path = path;
public IEnumerable<string> AsEnumerable() =>
File.ReadLines(_path);
public string[] AsArray() =>
File.ReadAllLines(_path);
}
На мой взгляд, вы используете один и тот же критерий для оценки двух совершенно различных подходов к написанию кода. Так можно и про 5.June() сказать: у числа не может быть июня или а что если 91.June() вызвать.
var a = x.GetValueOrDefault() — "взять значение у x или значение по умолчанию (семантика Nullable выводится из контекста вызова) и поместить его в переменную a".
var a = x.OrDefault — "a — это или x, или значение по умолчанию (семантика Nullable так же выводится)".
Первый вариант хорош, но императивен и избыточен. Ко второму нужно привыкнуть, ведь мыслится всё описательными категориями, а не производящимися вычислениями.
Дополню примером из Python, где синтаксис уже предполагает семантику or:
Максимум сходства с естественным языком обеспечивает разработчик. Язык программирования предоставляет для этого средства: лаконичный синтаксис, методы расширений, UFCS, шаблоны и макросы и т.д.
Что пользоваться этим всем можно не совсем элегантно — тоже верно.
Действие это или нет, различается не синтаксически и формально (метод или свойство), а семантически: скидка это, пользователь из базы, запись, добавление.
Это тот же самый хелпер, просто с другим синтаксисом.
Это не совсем верно.
Во-первых, методы расширений ограничены одним типом (если это хорошие, аккуратные методы расширений, а не очередные мусорки).
Во-вторых, методы расширений семантически идентичны вызовам методов на объекте, тогда как хелперы — вызовам статических методов с передачей объекта как параметра.
public interface IDiscount
{
(decimal Value, decimal AdditionalValue) Of(Customer customer, Order order);
}
Всегда можно поиграться и найти то, что подходит. Если на каждое улучшение находится "если", это не значит, что нужно вообще ничего не улучшать и писать повсюду: _discountCalculator.CalculateDiscount (ещё утверждается, что это DDD).
Тут с вами соглашусь! Пожалуй, действительно, слабый момент. Я очень долго думал над тем, как избежать неоднозначности в таких случаях, когда мы ограничиваем какое-то значение нижним или верхним пределом (здоровье не может опуститься ниже нуля).
Улучшить, вероятно, стоит так:
path.Without(x).AtEnd
определён для задач, предполагающих работу не только с расширениями файлов.К сожалению, C# не позволяет определить
path.Without(".exe")
так, чтобы он имел смысл, если не было вызвано никакое из продолжений вродеEnd
. Тем не менее, это решается с помощьюpath.Without(".exe").Everywhere
.Согласен, двусмысленность есть. Но она — в примере, в демонстрации, общий принцип остаётся таким же: использовать
using static
, чтобы упрощать понимание и читаемость некоторых участков кода.Приведу другой:
Разумеется, у
using static
есть свои нюансы (нужно, чтобы не было коллизий в именах), но, тем не менее, это инструмент!Вы всё верно поняли.
Одно и то же. Вы же не пишете
int? numberOrNull
, а пишетеint? number
(что, по вашей логике, неверно, поскольку там не число, а число или ничего). Некоторые детали отдаются типу и контексту, так что тут, на мой взгляд, всё в порядке.Более того, мне интересно, как это противоречит самой идее включать название переменной в имя метода и тем самым добиваться fluent.
Боюсь, я этого не утверждал...
Рекомендация сама по себе неплохая, но её слабость в следующем утверждении:
Это неверно.
Поэтому мы видим:
Where
,Select
и т.д.;Enumerable.Empty<T>()
;ImmutableArray.As<T>()
.Боюсь, чёткость этой разницы крайне сомнительна, а критерий производительности устарел. Советую взглянуть на вторую часть статьи: в большинстве случаев важна семантика, а не конкретные технические детали.
Я подразумевал такую штуку: если нужно показать действие (
Put
), то мы используем глагол; если нужно показать просто какую-то вещь как часть другой — не используем.Можете полагать значение с ключом x как бы свойством хешмапы, как, например, у яблока есть свойство вес.
Знаю, в Java всё равно пишут
getWeight()
, но, мне кажется, это кто-то зря придумал и очень-очень давно. Лучшеweight()
. Потому что вес — это не действие, которое яблоко умеет делать, а его свойство.Часть этих штук можно посмотреть у меня на GitHub.
Проблема в том, что
ExceptWith
ничего не возвращает.На мой взгляд, напротив: нужно всегда помнить, что у каждого вызова есть контекст, например, методы вызываются или в контексте имени переменной, или в контексте имени класса (если метод статический). Иначе получится
Directory.CreateDirectory
.Проектируя методы с опорой на контекст, например, на определённую форму слова переменной, мы тем самым ещё и заставляем разработчика избегать всяческих двусмысленностей и коверканий:
One
лучше подходит кusers
, чем кusersRepository
.И не подозревал о существовании
File.ReadLines
. Да уж, выглядит, конечно, не очень (с точки зрения API). Но я бы не хотел видетьFile.EnumerateAllLines
, которое, уверен, первым приходит в голову как хорошее решение.Понимая, что тут очень важно показать разницу между ленивым считыванием строчек из файла и неленивым, я бы, вероятно, написал нечто такое:
Не совсем понимаю, какое состояние меняется.
Вот есть словарь, а вот есть значение с ключом x. Что получается и что меняется?
Корректно: заказы все. Это значит — все заказы. Что это не вызов
LINQ
выведется из контекста.Она тоже неплоха и лаконична, хотя и не так читаема, как
x.Or(10)
.На мой взгляд, вы используете один и тот же критерий для оценки двух совершенно различных подходов к написанию кода. Так можно и про
5.June()
сказать: у числа не может быть июня или а что если 91.June() вызвать.var a = x.GetValueOrDefault()
— "взять значение уx
или значение по умолчанию (семантикаNullable
выводится из контекста вызова) и поместить его в переменнуюa
".var a = x.OrDefault
— "a
— это илиx
, или значение по умолчанию (семантикаNullable
так же выводится)".Первый вариант хорош, но императивен и избыточен. Ко второму нужно привыкнуть, ведь мыслится всё описательными категориями, а не производящимися вычислениями.
Дополню примером из Python, где синтаксис уже предполагает семантику
or
:Put
не будем, потому что это действие, и его мы хотим отразить.Get
будем, потому что это не действие, и его мы не хотим отражать.Максимум сходства с естественным языком обеспечивает разработчик. Язык программирования предоставляет для этого средства: лаконичный синтаксис, методы расширений, UFCS, шаблоны и макросы и т.д.
Что пользоваться этим всем можно не совсем элегантно — тоже верно.
А потом, следуя советам второй статьи, мы убираем
Get
, и получаетсяOrDefault
— лаконично и просто, без излишеств."гет" — это не действие.
Действие это или нет, различается не синтаксически и формально (метод или свойство), а семантически: скидка это, пользователь из базы, запись, добавление.
Это не совсем верно.
Во-первых, методы расширений ограничены одним типом (если это хорошие, аккуратные методы расширений, а не очередные мусорки).
Во-вторых, методы расширений семантически идентичны вызовам методов на объекте, тогда как хелперы — вызовам статических методов с передачей объекта как параметра.
Думаю, просто следует проявить изобретательность.
Всегда можно поиграться и найти то, что подходит. Если на каждое улучшение находится "если", это не значит, что нужно вообще ничего не улучшать и писать повсюду:
_discountCalculator.CalculateDiscount
(ещё утверждается, что это DDD).