Violations Of The “Tell, Don’t Ask” and “Don’t Repeat Yourself” Principles?
In the last few years, I’ve written a lot of code that looks like this:
1: public class IsThisATellDontAskViolation
2: {
3: public void DoBadThings()
4: {
5: if (something.CanHandle(anotherThing))
6: {
7: var response = something.GetThatFromIt(anotherThing);
8: DoSomethingWithTheResponse(response);
9: }
10: else
11: {
12: DoSomethingElse();
13: }
14: }
15: }
</div> </div>
Look at lines 5 and 7, specifically. In this code, I’ve got a call being made to a dependency to check whether or not the dependency can handle whatever it is I want to send to it. If the dependency can handle it, I call another method on the same object, passing in the same parameter and expecting to be given a result of the processing.
This code bothers me, honestly, but I find myself constantly writing it. It appears to me, to be a violation of both the Tell, Don’t Ask and Don’t Repeat Yourself (DRY) principles. I don’t like how the class questions the dependency and then has it do something based on the response. This seems like a violation of Tell, Don’t Ask and is clearly falling back to simple procedural programming techniques. Then, having the same “anotherThing” variable passed into another method on the same dependency object seems like its violating DRY. Why should I have to pass the same thing to the same object twice?
Another example of this type of code can be found in my SOLID Principles presentation and sample code. In the Open Closed Principle, I create the following code:
1: public string GetMessageBody(string fileContents)
2: {
3: string messageBody = string.Empty;
4: foreach(IFileFormatReader formatReader in _formatReaders)
5: {
6: if (formatReader.CanHandle(fileContents))
7: {
8: messageBody = formatReader.GetMessageBody(fileContents);
9: }
10: }
11: return messageBody;
12: }
</div> </div>
You can clearly see the same pattern with the CanHandle method on like 6 and the GetMessageBody method on line 8. For the same reasons, this code bothers me. It always has, but I’ve never done anything to correct it.
Is This A Violation Of Tell, Don’t Ask And/Or DRY?
Ultimately, that’s what I’m asking… are these code samples violating those principles? … and Why or Why Not? I would love to hear your opinion in the comments here or as your own blog post.
One Possible Solution
Assuming that this is a violation of those principles (and I’m all ears, listening for reasons why it is or is not), I have one solution that I’ve used a number of times for a similar scenario. Rather than having a method to check if the dependency can handle the data sent to it, just have one method that either processes the data or doesn’t, but tells you whether or not it did through the returned object.
1: public class Response<T>
2: {
3: public bool RequestWasHandled { get; private set; }
4: public T Data {get; private set;}
5: public Response(bool requestWasHandled, T data)
6: {
7: RequestWasHandled = requestWasHandled;
8: Data = data;
9: }
10: }
11:
12: public class TellDontAskCorrection
13: {
14: public void DoGoodThings()
15: {
16: var response = something.GetThatFromIt(anotherThing);
17: if (response.RequestWasHandled)
18: DoSomethingWithTheResponse(response.Data);
19: else
20: DoSomethingElse();
21: }
22: }
</div> </div>
This works. I’ve used it a number of times in a number of different scenarios and it has helped clean up some ugly code in some places. There is still an if-then statement in the DoGoodThings method but that may not be avoidable and may not be an issue since the method calls in the two if-then parts are very different. I don’t think this is the solution to the problems, though. It’s only one possible solution that works in a few specific contexts.
Looking For Other Solutions
What are some of the patterns, practices and modeling techniques that you are using to prevent Tell, Don’t Ask and DRY violations? Not just for the scenario that I’ve shown here, but for any given scenario where you find yourself wanting to introduce procedural logic that should be encapsulated into the object being called. I would love to see examples of the code you are dealing with and the solutions to that scenario. Please share – here in the comments (please use Pastie or Github Gist or something else that formats the code nicely if you have more than a couple lines of code, though) or in your own blog, etc.