Создаем IT-продукты на заказ

За 5 лет мы создали продукты с топовыми e-commerce компаниями, ритейлом, банками и другими бизнесами по всему миру.

Специализируемся на SaaS-решениях на архитектуре микросервисов, делаем аналитику IT-проекта перед стартом.

Посмотрите, что говорят о нас клиенты и как комфортно мы стартуем проекты.

Для обсуждения проекта пишите на ceo@byndyusoft.com или звоните +7 (904) 305 5263

среда, 29 октября 2008 г.

TDD и LLBLGen Pro

LLBLGen Pro - это O/R mapper, который генерирует .NET код, основываясь на схеме в базе данных.

Сейчас мы активно его используем для работы над нашим проектом. Надо отметить, что эта ORM имеет очень хороший интерфейс и настройку.

воскресенье, 26 октября 2008 г.

Тесты вперед?

Многие меня спрашивают: "Не жалко тебе тратить время на написание тестов?". Могу ответить коротко: "Нет".

суббота, 25 октября 2008 г.

Exception logic?

Недавно проводил собеседование. На тестовое задание вместе выбрали сделать консольный калькулятор. Суть работы в том, что вы вводите первый операнд, операцию и второй операнд.

> calculator.exe 1 + 2

После чего должен показаться результат.

Мы сели вместе за компьютер и начали конечно же с тестов. Постепенно вырисовывалась логика приложения. Мы решили, что у нас будет объект AgrumentParser, который принимает массив аргументов командной строки в конструкторе и проверяете их. Потом у этого класса можно узнать значение первого операнда, операцию, которую он уже преобразовал в enum, и второй операнд.

   1:  public enum OperationType
   2:  {
   3:      None = 0,
   4:      Add,
   5:      Min,
   6:      Plus,
   7:      Div
   8:  }
   9:   
  10:  public class ArgumentParser
  11:  {
  12:      private double firstOperand;
  13:      private double secondOperand;
  14:      private OperationType operationType;
  15:   
  16:      public ArgumentParser(string[] args)
  17:      {
  18:      // заполняем переменные firstOperand, secondOperand и operationType из args
  19:      }
  20:   
  21:      public double FirstOperand
  22:      {
  23:          get { return firstOperand; }
  24:      }
  25:   
  26:      public OperationType OperationType
  27:      {
  28:          get { return operationType; }
  29:      }
  30:   
  31:      public double SecondOperand
  32:      {
  33:          get { return secondOperand; }
  34:      }
  35:  }

К тому же мы завели очень интересный спор. Суть его вот в чем - если пользователь ввел аргументы в неверном формате (например, 1 плюс 2), как вести себя классу, который "оборачивает" эти аргументы? Если аргументы неверные, то как можно обратиться к полю FirstOperand? Должны мы выбрасывать исключение или же добавить поле IsArgumentValid в класс ArgumentParser?

Здесь нам виделось 2 основных подхода:

  1. Т.к. состояние объекта после передачи ему неверных аргументов будет не определено, то сам объект должен выбрасывать исключение. Соответственно его клиент, должен ловить это исключение и обрабатывать ошибку.
  2. После того, как создан класс ArgumentParser из параметров командной строки, мы должны узнать у него на сколько эти параметры верны, т.е. вызвать у него свойство IsArgumentValid.

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

В данном примере это абсолютно оправдано:

   1:  using (var adapter = new DataAccessAdapter(false))
   2:  {
   3:      try
   4:      {
   5:          adapter.SaveEntity(entity, false, false);
   6:      }
   7:      catch
   8:      {
   9:          adapter.Rollback();
  10:          throw;
  11:      }
  12:      adapter.Commit();
  13:  }

А что если ваш модуль при обращении к нему может выбрасывать свои определнные исключения? Как работать с такой "исключительной" логикой?

Так:

   1:  Customer customer = CustomerFactory.Create();
   2:  if (customer.IsLoaded)
   3:  {
   4:      // обычная логика
   5:  }
   6:  else
   7:  {
   8:      // альтернативная логика
   9:  }

или так:

   1:  try
   2:  {
   3:      Customer customer = CustomerFactory.Create();
   4:      // обычная логика
   5:  }
   6:  catch(NotLoadedException)
   7:  {
   8:      // альтернативная логика
   9:  }

Возвращаясь к примеру с консольным калькулятором.

Первый вариант:

   1:  var parser = new ArgumentParser(args);
   2:  if (parser.IsArgumentValid)
   3:  {
   4:      // работаем с аргументами и производим операцию выбранную пользователем
   5:  }
   6:  else
   7:  {
   8:      // показываем пользователю формат работы с нашим приложением
   9:  }

Второй вариант:

   1:  try
   2:  {
   3:      var parser = new ArgumentParser(args);
   4:      // работаем с аргументами и производим операцию выбранную пользователем
   5:  }
   6:  catch(ArgumentException)
   7:  {
   8:      // показываем пользователю формат работы с нашим приложением
   9:  }

Лично я склоняюсь к первому варианту. Какие будут мысли?



Примечание

Crypto привел отличный аргумент против логики построенной на исключениях.

пятница, 24 октября 2008 г.

Использование паттернов Strategy и Registry

Слишком много получилось писать в комментарии, решил вынести в отдельный пост.

В приведеном случае, когда логика постоянно меняется (пример кода в моем комментарии на пост Евгения), действительно лучше всего подойдет паттерн Visitor.

Но для такого простого варианта, когда классу ClassA соответствует определеная функция, лучше все-таки использовать Стратегию.

Хочу предложить модификацию реализации стратегии Евгения.

Лучше вынести информацию о сопоставлении типа класса к IHandler в отдельную сущность. Т.е. создать статический Регистр с тем же словарем.

   1:  public class BaseClass
   2:  {
   3:  }
   4:   
   5:  public class ClassA : BaseClass
   6:  {
   7:  }
   8:   
   9:  public interface IHandler<T> where T : BaseClass
  10:  {
  11:      void DoFor(T obj);
  12:  }
  13:   
  14:  public class MyClass
  15:  {
  16:      public void DoSomethingWith<T>(T obj) where T : BaseClass
  17:      {
  18:          // какая-то логика
  19:   
  20:          HanlderRegistry.Get<T>().DoFor(obj);
  21:      }
  22:  }
  23:   
  24:  public static class HanlderRegistry
  25:  {
  26:      private static readonly Dictionary<Type, Type> _handlers = InitHanlders();
  27:   
  28:      private static Dictionary<Type, Type> InitHanlders()
  29:      {
  30:          return new Dictionary<Type, Type>
  31:          {
  32:              {typeof (ClassA), typeof(HanlderForClassA)}
  33:          };
  34:      }
  35:   
  36:      public static IHandler<T> Get<T>() where T : BaseClass
  37:      {
  38:          Type hanlderType = _handlers[typeof (T)];
  39:          return Activator.CreateInstance(hanlderType) as IHandler<T>;
  40:      }
  41:  }
  42:   
  43:  internal class HanlderForClassA : IHandler<ClassA>
  44:  {
  45:      #region Implementation of IHandler
  46:   
  47:      public void DoFor(ClassA obj)
  48:      {
  49:          // логика работы с классом ClassA
  50:      }
  51:   
  52:      #endregion
  53:  }

Здесь, HanlderRegistry - это единственное место, в котором происходит сопоставление обработчиков и подклассов BaseClass.

Естественно, пример очень абстрасктный, но думаю, что основную логику рефакторинга он показывает.

Если приводить пример более приближенный к реальности, то таким образом можно связать объект и его маппер в базу данных.

Создаем IT-продукты на заказ

За 5 лет мы создали продукты с топовыми e-commerce компаниями, ритейлом, банками и другими бизнесами по всему миру.

Специализируемся на SaaS-решениях на архитектуре микросервисов, делаем аналитику IT-проекта перед стартом.

Посмотрите, что говорят о нас клиенты и как комфортно мы стартуем проекты.

Для обсуждения проекта пишите на ceo@byndyusoft.com или звоните +7 (904) 305 5263