пятница, 23 января 2015 г.

Типовые ошибки в коде

Небольшая заметка про ошибки, которые чаще всего встречаются в коде. Чего уже таить, когда-то я и сам их совершал. Поэтому надеюсь, что эта запись позволит кому-нибудь уберечься от них.

Эквивалентность

Это классическая проблема при проектировании типов. По-умолчанию, эквивалентность типов проверяется стандартным способом. Для ссылочных типов это object.ReferenceEquals, для значимых это сравнение всей структуры объекта. Чтобы описать свои законы эквивалентности для типа, нужно переопределить Equals и GetHashCode.
Если вы не абсолютно уверены, что можете описать эквивалентность для типа, лучше этого не делать вообще. Хорошо спроектированные типы, которые сравнивают объекты друг с другом, должны принимать IEqualityComparer.
Классическая ошибка - забыл GetHashCode переопределить

public class Account
{
    public string Login { get; set; }

    public override bool Equals(object obj)
    {
        return ((Account)obj).Login == Login;
    }
}

Семантическая ошибка. Эквивалентность, основанная на способе хранения, обработки и т.д., но не на семантике самого типа.

public class Account
{
    public int Id { get; set; }
    public string Login { get; set; }

    public override bool Equals(object obj)
    {
        return ((Account)obj).Id == Id;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

Строки

Самая большая проблема это, конечно, сравнение. Например, посмотрите самый первый код в заметке - используется ==. Строки будут сравниваться с использованием текущей культуры. В подавляющем большинстве вариантов, это не нужно. Используйте String.Equals с указанием конкретного StringComparison.

Вторая проблема - использование пустой строки "". Нужно понимать, что для каждой "" на машине выделяется память. Пусть включено интернирование, "" все равно в памяти будет выделено как минимум для поиска в уже загруженных CLR строках. Используйте string.Empty.

Перечисления, коллекции , списки

Большая проблема, это возврат в качестве значения IEnumerable<T> из методов null. Это скорее даже не ошибка, а дополнительная сложность. Тем не менее, чтобы было проще пользоваться вашим кодом, вместо null возвращайте пустое перечисление Enumerable.Empty<T>(). Там внутри используется статический объект для каждого типа, поэтому memory traffic будет минимальным. По этой же причине это лучше, чем возвращать new List<T>().

Не ищите по большим спискам через LinqToObject.

public class Sender
{
    private List<Account> _accounts = new List<Account>();

    public void Send(string login)
    {
        var email = _accounts.FirstOrDefault(x => x.Login.Equals(login, StringComparison.OrdinalIgnoreCase));

        ...
    }
}

Для FirstOrDefault есть шанс, что прохода по всему списку не будет. Для Where все будет уж совсем плохо - проход по каждому элементу гарантирован. Для таких поисков нужно лучше строить индексы на Dictionary<K,T>. Все это справедливо, если список большой и искать в нем надо часто.

Всегда старайтесь указать емкость для списков/коллекций/словарей при создании. Это экономит как скорость наполняемости, так и способствует уменьшению количеств обращения к памяти. Например, эта ошибка совершена в коде выше new List<Account>(). Большинство стандартных коллекций/списков/словарей внутри держит массив для ваших элементов. Если в массиве заканчивается место - реаллоцируются новые. бОльшие, участки памяти.

Вроде таких, часто встречающихся, больше нет.

2 комментария:

  1. Спасибо, но есть пара вещей.
    1. Примеры со сравнением у тебя, конечно, чисто учебные. В МСДН есть статья на эту тему
    2. По поводу инициализации массивов - это не ошибка. Желательно, конечно, указывать размер, если примерно представляешь, какой он должен быть. Но это не обязательно требование.

    Кстати, по поводу эквивалентности можно отдельную статью написать. Там довольно много интересного. Тот же интерфейс IEquatable[T] описать, и в каких случаях Equals и простое сравнение == может давать разные результаты.

    По поводу хеширования. У Рихтера вроде про это написано очень подробно. Там и правила, которым надо следовать и тд. Это тоже прям тема для отдельной статьи.

    ОтветитьУдалить
    Ответы
    1. Тут много про что отдельную статью можно написать.
      Ну да, формально я описал не только ошибки, но и рекомендации.

      Удалить