Леонард потратил много времени на то, что (как он считал) ему понадобится в будущем - забронировал столик ресторане, купил билеты в кино и т.п. Из-за этого он не успел сделать то, что важно для Пенни здесь и сейчас - купить цветы и одеться поприличнее. Если бы Леонард не пытался предсказать будущее, а делал то, что действительно нужно, шансов на свидание у него было бы больше. Кроме того, он сэкономил бы время, потраченное на то, что так и не пригодилось.
Приблизительно так же мы выглядим, когда пишем код, который может нам понадобится в будущем. К чему это приводит:
- тратится время, которое можно (и нужно) было потратить на добавление, тестирование и улучшение действительно необходимой сейчас функциональности
- становится больше кода, который нужно сопровождать
- ограничивается и усложняется добавление новой, действительно необходимой функциональности
Реализуйте функциональность только тогда, когда она действительно нужна, а не когда вы предвидете, что она вам понадобится.Этот принцип очень популярен в экстримальном программировании. В первую очередь это связано с применением методологии Scrum, где набор добавляемой функциональности чётко определяется перед началом очередной итерации, а приоритеты требованиям расставляет заказчик. Добавляя то, что отсутствует в бэклоге спринта, разработчик рискует не успеть сделать запланированную функциональность. Кроме того, он ставит собственное мнение о приоритетах выше мнения заказчика!
Например, нам необходимо сделать калькулятор, реализующий операции сложения, вычитания, умножения и деления. Всё очень просто. Начнём с операции сложения:
public class Calculator
{
public double Result { get; private set; }
public void Add(double value)
{
Result += value;
}
}
По-моему было бы здорово, если бы сохранялась история операций. Это можно сделать инкапсулировав операции в объекты-комманды. Чтобы не пришлось потом вносить много изменений, лучше сделать это сразу:
public class Calculator
{
public double Result { get; private set; }
private List<IOperation> _operations = new List<IOperation>();
public void Add(double value)
{
IOperation operation = new AdditionOperation(Result, value);
Result = operation.Execute();
_operations.Add(operation);
}
}
public interface IOperation
{
double Execute();
}
public class AdditionOperation : IOperation
{
private double _augend;
private double _addend;
public AdditionOperation(double augend, double addend)
{
_augend = augend;
_addend = addend;
}
public double Execute()
{
return _augend + _addend;
}
public override string ToString()
{
return String.Format("{0} + {1}", _augend, _addend);
}
}
Кстати, меня уже посещают мысли о логировании произведённых операций и локализации сообщений. Но вдруг звонит заказчик и спрашивает, готов ли калькулятор. Нет, он ещё не готов. И вообще, похоже мне надо взять больничный, а доделать калькулятор выпадает моему коллеге. Я как раз добавлял операцию вычитания, поэтому он увидит что-то вроде:
public class Calculator
{
public double Result { get; private set; }
private List<IOperation> _operations = new List<IOperation>();
public void Add(double value)
{
_operations.Add(new AdditionOperation(Result, value));
ExecuteLastOperation();
}
public void Subtract(double value)
{
_operations.Add(new SubtractionOperation(Result, value));
ExecuteLastOperation();
}
private double ExecuteLastOperation()
{
if (_operations.Count == 0)
return 0;
return _operations.Last().Execute();
}
}
public class SubtractionOperation : IOperation
{
private double _subtrahend;
private double _minuend;
public SubtractionOperation(double minuend, double subtrahend)
{
_minuend = minuend;
_subtrahend = subtrahend;
}
public double Execute()
{
throw new NotImplementedException();
}
}
// IOperation
// AdditionOperation
Коллега недоволен. С одной стороны ему надо разбираться в коде, который совершенно не нужен на данный момент. С другой стороны его подгоняет недовольный заказчик. Заказчик всегда прав, поэтому оставшиеся операции добавляются быстро:
public class Calculator
{
public double Result { get; private set; }
private List<IOperation> _operations = new List<IOperation>();
public void Add(double value)
{
_operations.Add(new AdditionOperation(Result, value));
_operations.Last().Execute();
}
public void Subtract(double value)
{
_operations.Add(new SubtractionOperation(Result, value));
_operations.Last().Execute();
}
public void Divide(double value)
{
Guard.NotZero(value);
Result = Result / value;
}
public void Multiply(double value)
{
Result = Result * value;
}
}
Так как история операций не является требуемой функциональностью, реализовывать её необязательно. В итоге калькулятор получился неоправданно сложным, с кучей ненужного кода, который ещё и работает лишь наполовину.
Принцип YAGNI помогает обуздать нашу фантазию и направить усилия на реализацию того, что действительно надо. Позже заказчик сам определит, на сколько важна отмена операций (если она вообще нужна). Кроме того, позже у нас будет больше знаний о системе и о функциональности, которую необходимо добавить. Например, может оказаться, что нужна возможность изменять параметры операций так, чтобы все последующие операции пересчитывались. Или параметры вообще будут не нужны - сойдёт и обычный стек результатов. Откуда нам знать эти нюансы сейчас? Поэтому наша первоочердная цель - простой, понятный код, не содержащий дублирования и готовый к добавлению новой функциональности:
public class Calculator
{
public double Result { get; private set; }
public void Add(double value)
{
Result += value;
}
public void Subtract(double value)
{
Result -= value;
}
public void Divide(double value)
{
Guard.NotZero(value);
Result = Result / value;
}
public void Multiply(double value)
{
Result *= value;
}
}
Допустим, нам необходимо сейчас добавить функциональность А. Почему возникает желание добавить функциональность Б, в которой сейчас нет необходимости?
Easy Now Hard Later - иногда кажется, что сейчас функциональность Б добавить проще, чем потом. На самом деле, если код хорошо факторизирован, это не будет сложно. Иначе - ненужная функциональность Б усложнит добавление нужной функциональности А.
Really Will Need It - функциональность Б действительно присутствует в списке требований, почему бы не добавить её сейчас? Потому что сейчас надо добавить функциональность А. Добавляя что-то другое можно выбиться из графика. Кроме того, любой список требований может измениться, например, если у заказчика закончатся деньги.
Might Lose The Idea - если не добавить функциональность Б сейчас, то о ней можно забыть. На практике не все идеи оказываются такими хорошими, какими кажутся вначале. Не все хорошие идеи одобряются заказчиком. В любом случае, существует масса способов не забыть идею - внести её в список задач, записать на бумажке, обычный TODO комментарий, в конце концов.
В общем, стоит добавлять лишь функциональность, которая действительно необходима для приложения. А все попытки добавить то, что возможно понадобится в будущем, лучше отложить до тех времён, когда вы разбогатеете на тотализаторе!

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