Что есть качественно написанный код? Это то, насколько он чист, понятен и насколько в нем легко разбираться. Путь развития навыка по написанию хорошего кода лежит через получение знаний и постоянную практику, а конечная цель – создание кода, понятного человеку с первого взгляда.
В данной статье будут приведены принципы и примеры, руководствуясь которыми, юные падаваны смогут немного продвинуться на пути познания философии качественного кода.
Это одна из тем, важность которой зачастую может быть недооценена разработчиком в стремлении как можно быстрее выкатить готовый продукт своего труда, однако этому вопросу следует уделять большое внимание.
Имена и названия должны нести смысл. Никакое форматирование и комментарии не помогут сделать код понятным, если Ваша переменная обозвана как x, a или какой-нибудь stringArray. Кроме того, если имена будут продуманы, можно эффективней пользоваться поиском по коду.
Названия должны быть произносимы и адекватной длины. Короткие и понятные имена воспринимаются лучше. Чтение кода не сильно отличается от чтения книги - когда мы его читаем, то проговариваем написанное в уме. Вид переменной или функции, чтение которой сломает язык или будет перегружено лишними словами, может сбить с мысли и отвлечь от понимания общей концепции кода.
// Не корректно
List<DateTime?> holidayDateTimeList;
List<IRole> employeeRoleList;
var unicornsQueen;
public List<IEmployee> GetListOfEmployeesOfActiveSubordinateDepartmentsBelongToOurUnit()
{
}
// Корректно
List<DateTime?> holidays;
List<IRole> roles;
var topManager;
public List<IEmployee> GetEmployeesFromSubDepartments()
{
}
Всегда нужно подписывать функции. Это повышает качество восприятия кода, а также экономит время при код-ревью, анализе кода или доработке. Документировать необходимо не только функции, но и константы, а также элементы перечислимых типов.
Смысл названия и подписи должны соответствовать содержимому. То есть подбирать имена для функций и их описания нужно так, чтобы они соответствовали функционалу. То же касается переменных и параметров функций. Избегайте любых двусмысленностей и ложных ассоциаций. Полагаю, не надо объяснять какой вред несет название, утверждающее одно, но делающее совершенно иное для того, кто пытается разобраться в написанном.
Однотипные именования. Одна идея (механика) - одно слово. Например, при добавлении элементов одинаковым образом используйте метод Add. Если механизм другого метода будет работать иначе, следует придумать другое слово (например, Insert, Append), описывающее новую концепцию. Если в системе уже используется метод GetUsers, то логичным будет использовать GetEmployees, а не FindEmployees или TakeEmployees, если нет необходимости намеренно обозначить различие между работой этих функций. Такой подход необходим для формирования интуитивно понятных имен.
Сокращения в именах идентификаторов не должны влиять на понятность имени. Не используйте аббревиатуры или неполные слова в идентификаторах, если только они не являются общепринятыми.
// Не корректно
CrtDlg
topMan
con
---
UserInterface
DataBase
// Корректно
CreateDialog
topManager
condition
---
UI
DB
При написании кода не забывайте проверять себя. Написали имя - посмотрите на него еще раз вне контекста. Раскрывает ли оно то, что представляет собой эта функция (переменная)? Думаю, смысл понятен. Желательно присваивать имя, максимально описывающее поведение функции, без необходимости проваливаться в нее при просмотре кода уровнем выше.
Ограничение по длине функции. Должно быть ограничение по длине функции (в среднем 50-100 строк), а также по длине строки, чтобы ее было комфортно читать. Не нужно пытаться поместить максимум логики в одно место. При написании или рефакторинге следует придерживаться разумной декомпозиции, то есть если функция протяженная и выполняет различные операции (например, создание документа, назначение прав, создание связей, отправка уведомлений и т.д.), то скорей всего разумным будет выделить логически связанные операции в отдельные функции.
Понятные имена аргументов. Тот же принцип именования - аргументы должны говорить сами за себя, а также их не должно быть слишком много. Если для работы функции нужно передать множество параметров, то стоит подумать о том, чтобы создать объект или структуру, логически объединяющий эти параметры и передавать его.
Комментарии. Обилие комментариев редко повышает читаемость кода, вместо этого лучше писать понятный код. Комментарии могут потерять свою актуальность в процессе доработок и рефакторинга, а сам код всегда будет достоверно говорить сам за себя. Также не нужно описывать комментариями очевидные вещи, это засоряет код и при наличии действительно важного комментария, есть шанс что его проигнорируют. Комментарии уместны для описания неочевидных вещей, когда передать определенную информацию кодом уже не удается.
Отступы между концепциями. Старайтесь отделять логически связанные блоки кода (концепции) внутри функции отступами (аналог абзацев в литературе). Простейшее правило, но его несоблюдение из любого кода делает текстовую кашу, в которой неприятно разбираться.
Совет 1: Лучше потратить 8 минут на обдумывание и 2 минуты на написание кода, чем наоборот. В будущем это сэкономит гораздо больше времени.
Хоть виды кода и описаны в справке DirectumRX, но не все начинающие разработчики об этом помнят.
Поэтому, время от времени в клиентском слое попадаются конструкции типа GetAll(), Create(), Save() и прочие методы, вызывающие запрос к БД и приводящие к потенциальным ошибкам.
Сами события сущности и обработчики, для которых DDS заботливо генерирует и располагает код сразу в нужном слое, тут я рассматривать смысла не вижу. Но как быть с кодом внутри них и с собственными функциями? Как запомнить куда их нужно помещать? Попробую сформулировать некоторые свои мысли, которые должны помочь Вам в поиске ответа на эти вопросы:
Попытайтесь представить логику, которую Вы описываете. Если она может выполняться с полезным результатом без участия пользователя - то, скорей всего, следует разместить ее в серверном слое.
Если же, наоборот, необходимо вывести окно сообщения или диалог для ввода данных от пользователя, тогда идем в клиентский слой.
Ну и самое сложное для понимания - разделяемый слой. В этот слой следует помещать функции, которым для работы не требуется ни взаимодействовать с пользователем, ни обращаться к базе данных (получать, создавать сущности). Как пример - функция для формирования имени по определенному шаблону.
Допустим, что для отображения какого-либо действия на ленте, нам нужно проверить наличие записей в базе данных. Студия генерирует нам обработчик события на клиентском слое, но делать подобную проверку напрямую с того же слоя будет не правильным:
public override bool CanDoSomething(Sungero.Domain.Client.CanExecuteActionArgs e)
{
var isExists = Dev.Example.Records.GetAll().Any();
return isExists;
}
Данное действие следует вынести в Remote функцию серверного слоя:
public override bool CanDoSomething(Sungero.Domain.Client.CanExecuteActionArgs e)
{
var isExists = Example.Docflow.PublicFunctions.Module.Remote.IsExists(_obj);
return isExists;
}
Мы рассмотрели пример по теме слоистости, однако тут есть свои нюансы. В примере выше вызов Remote-функции из обработчика возможности выполнения с точки зрения оптимизации не есть хорошо. Почему так и что с этим делать рассмотрим чуть позже в этой же статье.
Продолжая тему расположения кода, отмечу ситуацию, когда весь серверный код пишется в модуле. Это неправильно! Разумно располагать его там, где человек, не знакомый с ним, будет в первую очередь искать. Поэтому необходимо осмысленно группировать функции, константы и ресурсы локализации по объектам, т.е. код должен находиться в том объекте, с которым он работает.
Внутри кода используйте регионы (#region) для объединения логически связанных функций или обработчиков.
Вызываемые функции старайтесь располагать под основной или вызывающей функцией, это интуитивно понятней.
Дважды подумайте, прежде чем копировать уже написанный код! При таком подходе придется поддерживать одну и ту же логику и тестировать код сразу в двух или более местах, а изменив его в одном месте, нужно будет не забыть изменить и в другом.
Дублирование кода может происходить из-за незнания системы. Поэтому, перед написанием нового кода, необходимо ознакомиться с чем мы работаем. Используйте поиск - возможно, что необходимый функционал уже где-то реализован и разумным решением будет повторное использование кода.
// Пример для доступа к нестатическим методам:
Sungero.RecordManagement.PublicFunctions.ActionItemExecutionTask.Function();
// Пример для доступа к статическим методам:
Sungero.RecordManagement.Server.ActionItemExecutionTaskFunctions.Function();
Следует также не злоупотреблять данным принципом и использовать его разумно. Кроме того, бывают исключения, когда без дублирования не обойтись (в связи с ограничениями платформы).
Совет 2: Не думайте, что только что написанный Вами код идеален и не нуждается в доработке. Вернитесь к нему позже и посмотрите что можно улучшить. Развивайте свой код и себя.
Использование Equals()
Используйте метод сравнения Equals() для ссылочных типов. Для значимых типов используйте оператор равенства ==. В чем различия? Оператор == проверяет, ссылаются ли два объекта на одну область в памяти. Метод .Equals() переопределяют так, чтобы он сравнивал объекты по значениям их полей. К примеру, если классы двух объектов и все значения их полей совпадают, то эти два объекта считаются эквивалентными.
Проверка коллекций на наличие элементов
Если требуется проверить, что коллекция содержит 1 или более элементов внутри нее, то лучше использовать метод .Any() вместо метода .Count() > 0, поскольку .Count() работает медленнее и будет перебирать все элементы.
Проверка Nullable типов
Во избежание ошибок обязательно проверяйте Nullable типы через свойство .HasValue или методом .GetValueOrDefault(). Если мы попробуем получить через свойство .Value значение переменной, которая равна null, то получим ошибку. Свойство .HasValue возвращает true, если объект хранит некоторое значение, и false, если объект равен null.
Метод .GetValueOrDefault() возвращает значение переменной/параметра, если они не равны null. Если они равны null, то возвращается значение по умолчанию.
Проверка string на Null
При работе с пустыми строками есть риск получить исключение, чтобы этого избежать следует использовать метод String.IsNullOrEmpty(myStr). Результатом будет значение true, если параметр value равен null или пустой строке (string.Empty или ""); в противном случае — значение false.
Проверка возможности приведения Is, As
При обращении к полученным объектам, следует пользоваться функциями приведения .Is(), .As(). При использовании .As() всегда проверяйте возвращаемый им результат на null, либо используйте проверку возможности приведения .Is(). Невыполнение этого требования может привести к исключению NullReferenceException.
Зануление string переменных и свойств
Если необходимо очистить строковую переменную (свойство) вместо = "" используйте string.Empty. Преимущество использования состоит в том, что это более очевидно, чем пустая строка в кавычках и делает код более читабельным. Кроме того string.Empty по сути статическое свойство и при присвоении не создаст новый объект в памяти.
Опасности использования .First() и Single()
Метод .Single() выбирает единственную запись в выборке, и выдает исключение в двух случаях:
- если в выборке имеется более одной записи, удовлетворяющей заданном условию
- если в выборке нет ни одной записи, удовлетворяющей заданному условию
При этом .SingleOrDefault() выбирает единственную запись в выборке, и выдает исключение только в том случае, если в выборке имеется более одной записи, удовлетворяющей заданному условию.
.First() выбирает первую попавшуюся запись в выборке и выдает исключение в случае, если в базе не найдено ни одной записи, удовлетворяющей заданному условию.
По аналогии c .SingleOrDefault(), .FirstOrDefault() позволяет запросу возвращать null — значения. т.е., .FirstOrDefault() не осуществляет никаких проверок на количество записей в выборке.
.LastOrDefault(), .Last() работают так же, как и .FirstOrDefault(), First(), но выбирают не первую, а последнюю запись в выборке. При этом для большинства объектов Sungero данный метод не определён и рекомендуется сортировать по определенному свойству (например по дате), после чего вызывать .FirstOrDefault().
Методы .First() и .Single() работают быстрее, чем .FirstOrDefault() или .LastOrDefault(), но следует учитывать вероятность появления исключения и не использовать их там, где это может произойти.
Вычисляйте результат LINQ-запроса до того, как использовать в цикле
// Не корректно
foreach (var employee in Sungero.Company.Employees.GetAll().Where(e => e.Status = Sungero.Company.Employee.Status.Active))
{
employee.Name = employee.Name;
employee.Save();
}
// Корректно
var employees = Sungero.Company.Employees.GetAll()
.Where(e => e.Status = Sungero.Company.Employee.Status.Active)
.Where(e => e.Department.Id == depId);
foreach (var employee in employees)
{
employee.Name = employee.Name;
employee.Save();
}
При необходимости модификации последовательности .ToList() нужно использовать с осторожностью. Фильтрацию следует применять перед замыканием, т.е. сначала .Where(), потом .ToList(), а не наоборот и избегать помещения больших объемов данных в список.
// Не корректно
var employees = Sungero.Company.Employees.GetAll()
.ToList()
.Where(e => e.Department.Id == depId);
// Корректно
var employees = Sungero.Company.Employees.GetAll()
.Where(e => e.Department.Id == depId)
.ToList();
Не размещайте несколько инструкций на одной строке - это ухудшает читаемость.
// Не корректно
int a = 1; int b = 2;
// Корректно
int a = 1;
int b = 2;
После запятой должен быть пробел. После точки с запятой, если она не последняя в строке (например в инструкции for), должен быть пробел. Перед запятой или точкой с запятой пробелы не ставятся.
for (int i = 0; i < 10; i++)
this.DoSomething(1, 2, 3);
Все операторы должны быть отделены пробелом от операндов с обеих сторон.
int average = (1 + 2 + 3 + 4) / 4;
Блоки if, else, while должны иметь минимальный размер, чтобы информацию о них можно было держать в уме. Старайтесь избегать отрицательных условий где это не оправдано – на их восприятие обычно уходит чуть больше времени, чем на положительные.
// запись
if (condition)
// предпочтительнее записи
if (!condition)
Ниже показан пример одной и той же логики в разном оформлении. Использование операторов return и continue улучшает читаемость кода и делает его интуитивно понятным.
// Не корректно
if (firstCondition)
{
foreach (item in collection)
{
if (secondCondition)
{
if (thirdCondition)
{
// Какой-то код...
}
}
}
}
// Корректно
if (!firstCondition)
return;
foreach (item in collection)
{
if (!secondCondition || !thirdCondition)
continue;
// Какой-то код...
}
Открывайте и закрывайте парные скобки всегда в новой строке.
При написании LINQ запросов не нужно пытаться по максимуму скомпоновать условия в одно выражение. Чем больше условий, тем сложнее они воспринимаются, лучше разбить это выражение по условиям, так гораздо легче отследить логику выборки.
// Не корректно
query = query.Where(q => firstCondition && secondCondition && thirdCondition);
// Корректно
query = query.Where(q => firstCondition)
.Where(q => secondCondition)
.Where(q => thirdCondition);
Совет 3: Не пишите заумный код - пишите понятный.
При написании логических операций, следует, по возможности, выполнять более "дешевые" проверки перед более "дорогими":
// Не оптимально
if (!someFunction(param))
return;
if (param <= 0)
return;
// Какой-то код...
Функция someFunction() выполняет более ресурсоемкие операции, чем оператор сравнения. В таком случае рациональней будет поставить первой проверку переменной param, т.к. независимо от результата someFunction() дальнейший код не выполнится.
// Оптимально
if (param <= 0)
return;
if (!someFunction(param))
return;
// Какой-то код...
Также следует обращать внимание на порядок условий оператора if:
// Не оптимально
if (someFunction(param) && param > 0)
{
// Какой-то код...
}
При использовании логического оператора && всегда полностью вычисляется первый операнд. Второй операнд вычисляется только в том случае, если первый операнд имеет значение true. Если же первая проверка возвращает результат false, все дальнейшие проверки не выполняются. Значит предыдущий код следует написать так:
// Оптимально
if (param > 0 && someFunction(param))
{
// Какой-то код...
}
Тут же следует упомянуть обработчики событий. Вернемся к примеру, где мы разбирали слоистость кода. Обработчик события «Возможность выполнения» выполняется часто. Поэтому, если в нем есть серверные функции, то на веб-сервер отправляется большое количество запросов, что, в свою очередь, влияет на быстродействие системы.
Поэтому в данной ситуации следует выполнять проверку условия при показе формы и скрывать кнопку с помощью e.HideAction(), либо использовать параметр Params для хранения и обновления результата проверки:
public override bool CanDoSomething(Sungero.Domain.Client.CanExecuteActionArgs e)
{
bool isExists = false;
e.Params.TryGetValue(Constants.Docflow.Document.ParamsKeys.isExists, out isExists);
return isExists;
}
Эта статья задумывалась как наглядный пример неких размышлений, направленных на понимание идей и принципов качественного кода. В ней приведены частные примеры кода с рекомендациями, так как охватить все нюансы в данном формате довольно сложно, да и по скромному мнению автора, обучение не должно ограничиваться набором информации, полученной из конкретного источника.
Не менее важно - научиться правильно мыслить и постоянно развиваться, тогда наилучшие решения будут подбираться на интуитивном уровне. Материал статьи не является открытием и рассчитан на новичков, тем не менее если у Вас есть замечания или дополнения, прошу писать в комментариях. Хочу выразить благодарность коллегам, которые поделились опытом и идеями, а также тем, кто уделил время на прочтение данной статьи!
>>Данное действие следует вынести в Remote функцию серверного слоя:
Не надо так делать. В справке на этот счет написано
ВАЖНО. В обработчике события Возможность выполнения не рекомендуется вызывать серверные функции (с атрибутом Remote) и использовать логику, которая будет долго обрабатываться. Данный обработчик выполняется часто. Поэтому, если в нем есть серверные функции, то на веб-сервер отправляется большое количество запросов, что, в свою очередь, влияет на быстродействие системы.
Это событие вызывается на столько часто, что даже замедление на 0.02 сек может приводить к фризам до секунд при работе с сущностью. И не важно, напрямую вы идете получать данные из клиентского кода или через ремоут.
>> Поэтому в данной ситуации следует выполнять проверку условия при показе формы и скрывать кнопку с помощью e.HideAction(), либо использовать параметр Params для хранения и обновления результата проверки
А как оно будет работать для действий в списках? Получается такое действие никогда не будет доступно?
Андрей, спасибо за вопрос! Действительно, параметр Params применяется при работе с открытой карточкой и при проверке возможности выполнения из списка, если этого не учесть, может оказаться, что действие доступно не будет.
Как решение я бы предложил для действий, доступность которых задана не только в карточке, добавлять аналогичную проверку в обработчик события выполнения действия, а в возможности выполнения учитывать доступность параметра:
Возможно, реализация немного костыльная, но при необходимости оптимизации, имеет право на жизнь. Буду благодарен если Вы поделитесь со всеми своим мнением по решению данной проблемы.
>>
Позволю себе не согласиться. В foreach запрос вычисляется один раз. И здесь проблема только в длине выражения в foreach.
>> добавлять аналогичную проверку в обработчик события выполнения действия
Как вариант (наверное самый дешевый, хоть и не очень удобный для пользователя, особенно когда действие доступно при выборе нескольких записей).
Альтернативные варианты очень сильно зависят от конкретного действия и контекста работы.
1. Если это прикладное действие и оно не вынесено в список - забить :) Тогда в списках оно не будет выполняться. Для платформеных действий (например импортировать в новую версию) забивать нельзя даже если оно в список не вынесено. На сколько я помню, там canexecute выполняется в списках независимо от установки в DDS.
Ну и само кэширование в параметрах можно делать не в показе, а в canexecute (если в параметрах есть - брать из параметров, если нет - вычислить и поместить в параметр).
2. Если это какой нибудь справочник классификатор с очень ограниченным количеством записей - ИМХО тоже можно забить. Не думаю что наступит конец системе, если для 10 записей дернится remote ф-я в canexecute
3. делать так, что бы у проверяемой сущности было достаточно количество информации для самой проверки. В вашем примере "нам нужно проверить наличие записей в базе данных" подумать, а что это за данные, в какой момент они появляются. Вынести в сущность свойство, в котором хранить факт наличия этой записи и заполнять это свойство. Само собой это дороже, много доп кода, синхронизация данных и т.д. Так что не всегда применимо и оправданно.
>> Поэтому в данной ситуации следует выполнять проверку условия при показе формы
Не уверен что это стоит делать на показе формы. Если пользователь нажмет на кнопку отмена, состояние карточки вернется в "исходное" состояние (как в DDS настроено), а событие повторно не отработает. ИМХО тут лучше refresh использовать, оно гарантированно отрабатывает, а не только при открытии карточки.
Вместо этого всегда явно вычисляйте результат LINQ-запроса, используя ToList() или схожие методы.
https://club.directum.ru/webhelp/directumrx/web/index.html?sds_poluchenie_bolshogo_obema_dannuh.htm
На быстродействие может негативно влиять включение большого объема данных в список ToList(). При написании кода нужно помнить, что при работе с потенциально большими данными лучше использовать параметризованный тип IQueryable (запрос на получение данных). Он содержит только сущности. Например, IQueryableДенис, к сожалению в справке контекст не написан, где это так работает.
Я сильно сомневаюсь что в платформе переделаны стандартные .net методы.
по документации с MSDN для линк запросов есть два режима выполнения. Прямо сейчас или отложенный. Замыкание в список - это прямо сейчас. В foreach будет отложенное выполнение запроса. Т.е. если вы бегаете в цекле по IQueryable, то запрос будет выполнен в момент первой итерации цикла и останется в памяти приложения для последующих итераций. Там нет никакой порционной загрузки и т.д. будут получены все данные, как и для замыкания в лист.
Эта штука с порционной загрузкой справедлива для списков платформы (события фильтрации например), где платформа на вход принимает IQueryable. Там сама платформа организует постраничную загрузку.
Возможно я ошибаюсь, но это можно проверить через запись трасс. Ну либо разработчики платформы мб поправят :)
По вашей же ссылке на официальную документацию, пример:
и сравните с тем что вы пишете и советуете в статье.
Денис, не понял что вы имеете в виду.
>> По вашей же ссылке на официальную документацию
>>и сравните с тем что вы пишете и советуете в статье
Статью не я писал.
И то что в статье написано про linq, замыкание и т.д. - не совсем корректно. Денис Николаев выше уже написал об этом.
Пример, который вы процитировали из msdn, всего лишь показывает, что IQueryble это по большому счету не выполненный запрос, непосредственное получение данных из СУБД будет выполняться при проходе цикла.
Но и та ссылка на справку, которую вы скинули, тоже, на мой взгляд, не корректная для ситуации с обходом в цикле по IQueryble. Данные получаться все а не порциями.
Андрей, да, сорри перепутал с автором :) Мой комментарий больше к совету автора использовать ToList везде и всюду. Сталкивались с таким в разных решения, на 10 записях все ок, на 100 000 РХ умирает.
Денис, спасибо за комментарий! Внес корректировку в текст статьи.
Денис, тут на самом деле не в системе дело, оно и на 100к может работать нормально (проверено :) ).
Там все упирается в наличие железа на серверах приложений под такие выборки, в первую очередь оперативная память.
Если у нас есть достаточное количество оперативы и мы можем гарантировать что выборка осуществляется в единицу времени один раз (например фоновый процесс), то оно может и не умереть.
Если мы этого гарантировать не можем (например пользовательский код или асинхронки), то там никакого железа не хватит.
Т.к. много "но и если", то лучше так не делать или подходить к этому с пониманием всех рисков и технических деталей.
Небольшой хак, как можно подобные вещи делать. Система съедает много оперативы т.к. при простом замыкании формата
в память грузятся сущности целиком со всеми свойствами (на выходе получаем лист объектов).
Если сделать вот так
то на выходе список будет съедать на много меньше памяти (там будут только Ид сущностей). Потом уже по этому списку можно организовать работу несколькими вариантами, либо просто в цикле по Ид получать сущность и выполнять нужные действия с ней, либо пробовать по этому списку самостоятельно организовать подобие порционной загрузки.
Если переменные нельзя называть unicornsQueen, то в чём вообще тогда смысл программирования?
Антон, но, как я понимаю, MO9l_nepeMEHHA9l_DJl9l_PE3YJlbTATA можно? Имя само за себя говорит))
Разговор про качественный и некачественный код следует начинать с экономики этого самого кода, ответив в первую очередь на вопрос "зачем", и только после этого рассказав "как". Из статьи не очень понятно, почему вообще надо писать "качественный" код (вот из этой статьи, например, сразу ясно, однако она больше про менеджмент). Если я напишу код, который выполняет свою задачу, и делает это эффективно, но при этом забуду про стандарты и рекомендации - качественный ли это код или некачественный?
Позволю себе не согласиться. В foreach запрос вычисляется один раз. И здесь проблема только в длине выражения в foreach.
Денис, думаю имеется ввиду что второй вариант значительно лучше читается, к тому же при дебаге выделение отдельной переменной может быть полезно.
Ну и само кэширование в параметрах можно делать не в показе, а в canexecute (если в параметрах есть - брать из параметров, если нет - вычислить и поместить в параметр).
Андрей, а иногда и вообще можно оставлять кнопку, но выполнять проверку в момент вызове действия
Касаемо "Использование Equals()
Используйте метод сравнения Equals() для ссылочных типов. Для значимых типов используйте оператор равенства ==. В чем различия? Оператор == проверяет, ссылаются ли два объекта на одну область в памяти. Метод .Equals() переопределяют так, чтобы он сравнивал объекты по значениям их полей. К примеру, если классы двух объектов и все значения их полей совпадают, то эти два объекта считаются эквивалентными."
строки ведь ссылочного типа, но конкретно строки рекомендуют сравнивать через ==
а вот статья касаемо сравнения ссылочного типа данных
В C# правильно сравнивать строки и через
==
, и черезEquals
. Но более предпочтительным будет сравнивать через==
.Почему так?
Метод
Equals
подразумевает сравнение значений объектов ссылочного типа, он объявлен какvirtual
и для строк он перегружен и сравнивает их, как и предполагается, по значению. В Ваших классах Вы должны давать свою реализацию для него. Иначе он будет вести себя какReferenceEquals
и для ссылок, которые указывают не на один объект будет даватьfalse
, хоть они и будут равны по значению.Оператор
==
для строк представляют свою реализацию, отличную от стандартной для всех других объектов ссылочного типа. Если сравниваемые ссылки имеют типSystem.String
, то он сначала сравнит указывают ли ссылки на один тот же объект и если нет, то будет сравнивать две ссылки типаSystem.String
по значению.Но маленькое замечание, если мы сравниваем объект типа
System.String
(string
) с объектом типаSystem.Object
(object), который указывает на строку, они будут сравниваться по значениям.Пример:
result
будет равенtrue
, так какobj
приведётся к типуstring
, потому что методEquals
мы вызвали у строки (объекта типаstring
).Но если мы сравниваем объект типа
System.Object
(object), который указывает на строку, с объектом типаSystem.String
(string
) они будут сравниваться по ссылке.Пример:
result
будет равенfalse
, так какstr
приведётся к типуobject
, потому что методEquals
мы вызвали у объекта типаobject
.Поэтому в данном случае важно привести
object
к типуstring
https://ru.stackoverflow.com/questions/621921/%D0%9A%D0%B0%D0%BA-%D0%B2-c-%D0%BF%D1%80%D0%B0%D0%B2%D0%B8%D0%BB%D1%8C%D0%BD%D0%BE-%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B8%D0%B2%D0%B0%D1%82%D1%8C-%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8
Статья определённо активизировала комьюнити, и это здорово.
В целом изложение выглядит односторонним, указание делать именно так, а почему? Материала много и ощущение, что его должно быть ещё больше, имею ввиду указание причины советуемых подходов.
Так например:
"Фильтрацию следует применять перед замыканием, т.е. сначала .Where(), потом .ToList(), а не наоборот и избегать помещения больших объемов данных в список." - выдан очень странный пример, кто-то серьёзно видел этот некорректно описанный вариант? Тут явно напрашивается объяснение, "почему?", отчего зависит требование к аппаратным ресурсам при одном и при другом подходе. Смотрим как/где работает IEnumerable и IQuaryable, чем они отличаются, и вопросы какое использование будет корректным отпадает. Возможно даже отпадёт желание использовать перевод в IList() - но далеко не во всех случаях.
или
"При написании LINQ запросов не нужно пытаться по максимуму скомпоновать условия в одно выражение." - а что насчёт сложности формирования тела запроса, который в итоге получается? какой из подходов будет выглядеть более оптимальным? Чем плох подход с переносом каждого условия на отдельную строку, без вызова каждый раз .Where(), читаемость в этом случае будет на том же уровне? Стопка вопросов, которая ведёт к копанию дополнительной информации, что в целом тоже неплохо.
Дмитрий, Как заметил Роб Пайк (Rob Pike), "сложность мультипликативна”: устранение проблемы путем усложнения одной части системы медленно, но верно добавляет сложность в другие части. Постоянное требование внесения новых функций, настроек и конфигураций очень быстро заставляет отказаться от простоты, несмотря на то что в долгосрочной перспективе простота является ключом к хорошему программному обеспечению. Простота требует большего количества работы в начале проекта по определению самой сути идеи и большей дисциплины во время жизненного цикла проекта, которая поможет отличать хорошие изменения от плохих. При достаточных усилиях хорошие изменения, в отличие от плохих, могут быть приняты без ущерба для того, что Фред Брукс (Fred Brooks) назвал “концептуальной целостностью” проекта. Плохие же изменения всего лишь разменивают простоту на удобство. Только с помощью простоты дизайна система может в процессе роста оставаться устойчивой, безопасной и последовательной."
Авторизуйтесь, чтобы написать комментарий