Это одна из наших самых больших проблем, поскольку у нас есть несколько клиентов, которые используют одну и ту же кодовую базу, но имеют самые разные потребности. Позвольте мне поделиться с вами историей нашей эволюции:
Наша компания начинала с одного клиента, и когда у нас начали появляться другие клиенты, в коде вы начали бы видеть такие вещи:
if(clientName == "ABC") {
// do it the way ABC client likes
} else {
// do it the way most clients like.
}
В конце концов мы поняли, что это делает код действительно уродливым и неуправляемым. Если другой клиент хотел, чтобы его клиент вел себя как ABC в одном месте и CBA в другом, мы застряли. Поэтому вместо этого мы обратились к файлу .properties с кучей точек конфигурации.
if((bool)configProps.get("LastNameFirst")) {
// output the last name first
} else {
// output the first name first
}
Это было улучшением, но все еще очень неуклюжим. «Волшебные струны» предостаточно. Не было никакой реальной организации или документации по различным владениям. Многие свойства зависят от других свойств и ничего не будут делать (или даже что-то сломают!), Если не используются в правильных комбинациях. Большая часть (возможно, даже большая часть) нашего времени в некоторых итерациях была потрачена на исправление ошибок, которые возникли из-за того, что мы «исправили» что-то для одного клиента, что нарушало конфигурацию другого клиента. Когда у нас был новый клиент, мы просто начинали с файла свойств другого клиента, конфигурация которого «больше всего похожа» на ту, которую хотел этот клиент, а затем пытались настраивать вещи, пока они не выглядели правильно.
Мы пытались использовать различные методы, чтобы сделать эти точки конфигурации менее громоздкими, но добились лишь умеренного прогресса:
if(userDisplayConfigBean.showLastNameFirst())) {
// output the last name first
} else {
// output the first name first
}
Было несколько проектов по получению контроля над этими конфигурациями. Один из них заключался в написании механизма просмотра на основе XML, чтобы мы могли лучше настраивать дисплеи для каждого клиента.
<client name="ABC">
<field name="last_name" />
<field name="first_name" />
</client>
Другой проект включал в себя написание системы управления конфигурацией для консолидации нашего кода конфигурации, обеспечения того, чтобы каждая точка конфигурации была хорошо документирована, позволяла суперпользователям изменять значения конфигурации во время выполнения и позволяла коду проверять каждое изменение, чтобы избежать получения недопустимой комбинации. значений конфигурации.
Эти различные изменения определенно облегчили жизнь с каждым новым клиентом, но большинство из них не помогло устранить корень наших проблем. Изменение, которое действительно принесло нам наибольшую пользу, произошло, когда мы перестали рассматривать наш продукт как серию исправлений, чтобы что-то работало для еще одного клиента, и мы начали рассматривать наш продукт как «продукт». Когда клиент попросил новую функцию, мы начали внимательно рассматривать такие вопросы, как:
- Сколько других клиентов смогут использовать эту функцию сейчас или в будущем?
- Можно ли реализовать это так, чтобы наш код не стал менее управляемым?
- Можем ли мы реализовать другую функцию, отличную от того, о которой они просят, которая по-прежнему отвечала бы их потребностям, но была бы более подходящей для повторного использования другими клиентами?
При реализации функции мы будем смотреть в будущее. Вместо того, чтобы создавать новое поле базы данных, которое будет использоваться только одним клиентом, мы могли бы создать целую новую таблицу, которая могла бы позволить любому клиенту определять любое количество настраиваемых полей. Это потребует больше предварительной работы, но мы могли бы позволить каждому клиенту настраивать свой собственный продукт с большой степенью гибкости, не требуя от программиста изменения какого-либо кода.
Тем не менее, иногда есть определенные настройки, которые вы действительно не можете выполнить, не вложив огромных усилий в сложные механизмы правил и так далее. Когда вам просто нужно заставить его работать одним способом для одного клиента и другим - для другого, я обнаружил, что лучше всего программировать для интерфейсов и использовать внедрение зависимостей strong >. Если вы следуете «твердым» принципам, чтобы убедиться, что ваш код написан модульно с хорошим «разделением проблем» и т. Д., То не так уж больно изменить реализацию определенной части вашего кода для конкретного клиента:
public FirstLastNameGenerator : INameDisplayGenerator
{
IPersonRepository _personRepository;
public FirstLastNameGenerator(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public string GenerateDisplayNameForPerson(int personId)
{
Person person = _personRepository.GetById(personId);
return person.FirstName + " " + person.LastName;
}
}
public AbcModule : NinjectModule
{
public override void Load()
{
Rebind<INameDisplayGenerator>().To<FirstLastNameGenerator>();
}
}
Этот подход усилен другими методами, о которых я упоминал ранее. Например, я не писал AbcNameGenerator
, потому что, возможно, другие клиенты захотят аналогичного поведения в своих программах. Но, используя этот подход, вы можете довольно легко определять модули, которые переопределяют настройки по умолчанию для конкретных клиентов, очень гибким и расширяемым способом.
Поскольку такие системы по своей природе хрупки, также важно сосредоточиться на автоматическом тестировании: модульные тесты для отдельных классов, интеграционные тесты, чтобы убедиться (например), что все ваши привязки для инъекций работают правильно, и системные тесты, чтобы убедиться, что все работает вместе без регресса.
PS: Я использую слово «мы» на протяжении всей истории, хотя на самом деле я не работал в компании большую часть ее истории.
PPS: Простите за смесь C # и Java.
person
StriplingWarrior
schedule
08.02.2011