Pull to refresh
17
0
Джошуа Лайт @JoshuaLight

Software Developer

Send message

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


Улучшить, вероятно, стоит так:


Creature creature = ...;
int damage = ...;

// Было.
creature.Health = (creature.Health - damage).Or(0).IfLess();

// Стало.
creature.Health = (creature.Health - damage).ButNotLess(than: 0);
// Реальный классический подход
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.

Боюсь, я этого не утверждал...

Рекомендация сама по себе неплохая, но её слабость в следующем утверждении:


Because methods are the means of taking action

Это неверно.


Поэтому мы видим:


  • Where, Select и т.д.;
  • Enumerable.Empty<T>();
  • ImmutableArray.As<T>().
  • И т.д.

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

Я и уточнил, что подразумевается под «это не действие»

Я подразумевал такую штуку: если нужно показать действие (Put), то мы используем глагол; если нужно показать просто какую-то вещь как часть другой — не используем.


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


Знаю, в Java всё равно пишут getWeight(), но, мне кажется, это кто-то зря придумал и очень-очень давно. Лучше weight(). Потому что вес — это не действие, которое яблоко умеет делать, а его свойство.

Часть этих штук можно посмотреть у меня на GitHub.

items.ExceptWith(other) на взгляд означает: вернуть items за исключением other,

Проблема в том, что ExceptWith ничего не возвращает.

Рассчитывать на контекст — плохая идея. Апи должно быть читабельным даже без него.

На мой взгляд, напротив: нужно всегда помнить, что у каждого вызова есть контекст, например, методы вызываются или в контексте имени переменной, или в контексте имени класса (если метод статический). Иначе получится 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);
}

Не совсем понимаю, какое состояние меняется.


Вот есть словарь, а вот есть значение с ключом x. Что получается и что меняется?

заказы всё

Корректно: заказы все. Это значит — все заказы. Что это не вызов LINQ выведется из контекста.


UPD: для nullable есть простая старая конструкция:
int? x = null;
int value = x ?? 10;

Она тоже неплоха и лаконична, хотя и не так читаема, как x.Or(10).

На мой взгляд, вы используете один и тот же критерий для оценки двух совершенно различных подходов к написанию кода. Так можно и про 5.June() сказать: у числа не может быть июня или а что если 91.June() вызвать.


var a = x.GetValueOrDefault()"взять значение у x или значение по умолчанию (семантика Nullable выводится из контекста вызова) и поместить его в переменную a".


var a = x.OrDefault"a — это или x, или значение по умолчанию (семантика Nullable так же выводится)".


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


Дополню примером из Python, где синтаксис уже предполагает семантику or:


for item in items or []

Put не будем, потому что это действие, и его мы хотим отразить.
Get будем, потому что это не действие, и его мы не хотим отражать.

Максимум сходства с естественным языком обеспечивает разработчик. Язык программирования предоставляет для этого средства: лаконичный синтаксис, методы расширений, UFCS, шаблоны и макросы и т.д.


Что пользоваться этим всем можно не совсем элегантно — тоже верно.

можно сократить до GetOrDefault

А потом, следуя советам второй статьи, мы убираем Get, и получается OrDefault — лаконично и просто, без излишеств.

"гет" — это не действие.


Действие это или нет, различается не синтаксически и формально (метод или свойство), а семантически: скидка это, пользователь из базы, запись, добавление.

Это тот же самый хелпер, просто с другим синтаксисом.

Это не совсем верно.


Во-первых, методы расширений ограничены одним типом (если это хорошие, аккуратные методы расширений, а не очередные мусорки).
Во-вторых, методы расширений семантически идентичны вызовам методов на объекте, тогда как хелперы — вызовам статических методов с передачей объекта как параметра.

Думаю, просто следует проявить изобретательность.


public interface IDiscount
{
    (decimal Value, decimal AdditionalValue) Of(Customer customer, Order order);
}

Всегда можно поиграться и найти то, что подходит. Если на каждое улучшение находится "если", это не значит, что нужно вообще ничего не улучшать и писать повсюду: _discountCalculator.CalculateDiscount (ещё утверждается, что это DDD).

Information

Rating
Does not participate
Location
Харьков, Харьковская обл., Украина
Date of birth
Registered
Activity