У меня есть часть программного обеспечения, написанная с плавным синтаксисом. Цепочка метода имеет окончательное "окончание", перед которым в коде не делается ничего полезного (думаю, что генерация запросов NBuilder или Linq-to-SQL на самом деле не ударяет по базе данных до тех пор, пока мы не перейдем к нашим объектам, скажем, ToList()).
Проблема, с которой я столкнулась, - это путаница среди других разработчиков относительно правильного использования кода. Они пренебрегают вызовом метода "окончания" (таким образом, никогда не "ничего делать" )!
Меня интересует принудительное использование возвращаемого значения некоторых моих методов, чтобы мы никогда не могли "закончить цепочку", не называя "Finalize()" или "Save()" метод, который фактически выполняет работу.
Рассмотрим следующий код:
//The "factory" class the user will be dealing with
public class FluentClass
{
//The entry point for this software
public IntermediateClass<T> Init<T>()
{
return new IntermediateClass<T>();
}
}
//The class that actually does the work
public class IntermediateClass<T>
{
private List<T> _values;
//The user cannot call this constructor
internal IntermediateClass<T>()
{
_values = new List<T>();
}
//Once generated, they can call "setup" methods such as this
public IntermediateClass<T> With(T value)
{
var instance = new IntermediateClass<T>() { _values = _values };
instance._values.Add(value);
return instance;
}
//Picture "lazy loading" - you have to call this method to
//actually do anything worthwhile
public void Save()
{
var itemCount = _values.Count();
. . . //save to database, write a log, do some real work
}
}
Как вы можете видеть, правильное использование этого кода было бы чем-то вроде:
new FluentClass().Init<int>().With(-1).With(300).With(42).Save();
Проблема в том, что люди используют ее таким образом (думая, что она достигает того же, что и выше):
new FluentClass().Init<int>().With(-1).With(300).With(42);
Так широко распространена эта проблема, которая с совершенно хорошими намерениями другой разработчик однажды фактически изменила имя метода "Init", указав, что THAT-метод выполняет "настоящую работу" программного обеспечения.
Логические ошибки, подобные этим, очень трудно обнаружить и, конечно же, они компилируются, потому что вполне приемлемо вызывать метод с возвращаемым значением и просто "притворяться", он возвращает void. Visual Studio все равно, если вы это сделаете; ваше программное обеспечение все равно будет компилироваться и запускаться (хотя в некоторых случаях я считаю, что оно вызывает предупреждение). Конечно, это отличная возможность. Представьте простой метод "InsertToDatabase", который возвращает идентификатор новой строки как целое число - легко видеть, что есть некоторые случаи, когда нам нужен этот идентификатор, и в некоторых случаях, когда мы могли бы обойтись без него.
В случае с этим программным обеспечением окончательно нет никаких оснований отследить эту функцию "Сохранить" в конце цепочки методов. Это очень специализированная утилита, и только выигрыш приходит от последнего шага.
Я хочу, чтобы какое-то программное обеспечение выполнялось с ошибкой на уровне компилятора, если они вызывают "С()", а не "Сохранить()".
Это кажется невозможной задачей традиционными способами - но почему я прихожу к вам, ребята. Есть ли атрибут, который я могу использовать для предотвращения того, чтобы метод был "cast to void" или некоторые такие?
Примечание.. Альтернативный способ достижения этой цели, который уже был предложен мне, - это написать набор модульных тестов для обеспечения соблюдения этого правила и использовать что-то вроде http://www.testdriven.net, чтобы связать их с компилятором. Это приемлемое решение, но я надеюсь на что-то более элегантное.