История одного приложения

9 мар. 2011 г. | | |

История эта началась давно, в те времена, когда деревья были высокими и трава зеленее, да и макароны назывались правильно ... И была поставлена задача - написать простенькое приложеньице, дополнение к главной программе, состоящее буквально из одной формы.
Задача приложения была в том, чтобы записывать в БД главного приложения следующее: сколько конкретный работник потратил времени на  выполнение конкретной задачи с известной стоимостью (цена в час) для определенного клиента. Данные (клиент (Cusotmer), исполнитель (Supplier), сервис (Task), цена) брались из БД главного приложения. Что может быть проще? На форму были накиданы контролы, к контролам привязаны события - вуаля! Работает, готово.
Тут заказчик говорит: "Да, здорово, но еще здоровее будет, если можно будет изменять путь к БД главного приложения!" Да не проблема! Вот еще одна кнопочка, диалог, файл с сеттингами - всё работает! На этом этапе развития приложение состоит из...из... трудно сказать для такого "масштабного" приложения. Одна форма, пара кнопок - зачем заморачиваться и что-то выделять? В коде формы нет sql-кода, и на этом спасибо :) За работу с БД отвечает статический класс DBManager:
static class DBManager{
public static IList<Customers> GetCustomers() {...}
public static IList<supplier> GetSuppliers() {...}
public static IList<Task> GetTasks() {...}
...
}

Разработчик рад - работает, заказчик рад - работает! В данном раунде скорость (лень) победила качество.
Проходит время и ... "Общую стоимость нужно вычислять по-другому! " - говорит заказчик. - "Общая стоимость может включать в себя налог или нет в зависимости от типа компании!"  Разработчик открывает код класса Task, добавляет private member companyType, которую инициализирует в конструкторе класса, находит метод GetTotalPrice() и пишет:
if ( CompanyType.Brutto == companyType) { CalculateBruttoPrice();} else {CalculateNettoPrice();}

Но тут на подстанции сгорает трансформатор, и гаснет свет, и весь вышеприведенный код исчезает, не сохранившись на ограниченных просторах жесткого диска.
На следующий день разработчик еще раз прочитал требования заказчика, и выделил интерфейс ITask, создал базовый абстрактный класс BaseTask и конечные классы BruttoTask и NettoTask, различающиеся только механизмом расчета итоговой стоимости и налога:
interface ITask {
   ...
   double GetTotalPrice();
   double GetTotalTax();
}
/*когда стоимость налога включена в цену*/
class BruttoTask : BaseTask, ITask {
public double GetTotalPrice(){
    retrun Amount * Price;
}
public double GetTotalTax(){
   var hundrendPercentsPlusTax = 100 + TaxPercentage;
   return (GetTotalPrice() * TaxPercentage / hundrendPercentsPlusTax);
}
}
/*когда стоимость налога не включена в цену*/
class NettoTask : BaseTask, ITask {
public double GetTotalPrice(){
   return Amount * Price + GetTotalTax();
}
public double GetTotalTax(){
   return (Price*Amount * TaxPercentage) / 100;
}


После этого разработчик добавил класс-фабрику TaskFactory, которая будет в зависимости от типа компании создает нужный тип сервиса.
static class TaskFactory{
public ITask CreateTask(){
    if (CurrentSettings.CompanyType ==  CompanyTypes.Brutto) return new BruttoTask();
    if (CurrentSettings.CompanyType ==  CompanyTypes.Netto) return new NettoTask();
}
}

Ура, заказчик доволен, дело сделано! На этом этапе качество начинает побеждать скорость. Приложение стало немного более гибким. Например, если теперь заказчик скажет, что нужно добавить расчеты для компаний, у которых нет налога, то это потребует совсем не много усилий и займет не так уж много времени - всего лишь добавить class WithoutTaxTask : ITask.
Проходит N времени, и заказчику нужно немного расширить возможности приложения. Теперь нужно, чтобы программа умела сохранять, сколько и чего конкретный работник продал. Разработчик добавляет класс Product, но понимает, что уж очень он похож на Task, и поэтому в интерфейс ITask доавляет свойство IsService, которое и будет отличать сервис от продукта. Теперь нужно как-то получать список продуктов из БД и сохранять их туда. Разработчик открывает свой класс DBManager, начинает добавлять новый метод GetProducts(), и опять какие-то неполадки на подстанции уничтожают результаты его труда. На следующий день разработчик получает email от разработчика главной программы, в котором говорится, что было введено несколько служебных типов сервисов и продуктов, которые ни в коем случае отображать пользователю не нужно. Разработчик отрывает проект, ищет DBManager, начинает править GetTasks() и восклицает "Это ужасно!" И быстро начинает писать:
interface IEntity { int ID {get;set;} }
interface ITask : IEntity {…}
class Customer : IEntity {…}
class Supplier : IEntity { … }
class Service : IEntity {...}

interface IRepository<T> where T : class, IEntity{
   IList<T> Load();
}
class CustomerRepository : IRepository<Customer>{
public IList<Customer> Load() { … }
}
...
public interface ITaskRepository{
   IList<ITask> Load();
   bool Save(ITask task);
}

public class ServiceTaskRepository : IRepository<ITask>, ITaskRepository{
public IList<ITask> Load(){...}
public bool Save(ITask tsk){...}
}

Когда он заканчивает, то имеет Entities (хотя тут можно выделить Customer, Supplier, Service как Value-объекты, т.к. в процессе работы они никак не изменяются) и Repositories для них, счастливо улыбается и удаляет DBManager, который был перегружен обязанностями. Теперь каждый класс выполняет только свои обязанности, all inclusive больше не в моде. Разработчик открывает главную форму, код которой разросся на несколько экранов. "Нужно что-то с этим делать"-думает он и принимается за рефакторинг. По-тихоньку из бревна начал получаться Буратино, если б не сломался тесак. Разработчик со вздохом еще раз окинул взглядом код формы, и полез в свой арсенал выбирать рубанок калибра MVC / MVP / etc, но...
Но тут звонит заказчик, просит добавить в программу возможность просмотра закрытых сервисов и просит все это закончить «до завтра». Разработчик легко добавляет новый class ClosedTaskRepository : IRepository <ITask>, но опять тонет в коде главной формы. Выбравшись из трясины после добавления списка на форму, тестирует, отсылает заказчику и закрывает IDE: "На сегодня хватит. А завтра я разберусь с этими макаронами на главной форме". На следующий день довольный заказчик лукаво спрашивает "А можно сделать так, чтобы ….?" "Конечно, можно" - говорит разработчик, одевая макаронолазный костюм...
Мораль.... Какая тут мораль? Делайте сразу качество, даже если заказчик говорит "два поля одна кнопка", и очень хочется  по-быстрому все свалить в одну кучу.

0 коммент.:

Отправить комментарий