Design Tip – Write Honest Methods

Problem

As developers we learn that giving meaningful names to methods produces clean, readable and maintainable code. However, that’s not all we should be concerned about when it comes to method signatures. There are two other aspects of a method signature that must be given consideration when writing code: a) parameters b) return value.

Let’s look at signature of a method we’ve defined in our repository, what does this tell you:

It promises that if you pass int, you will receive a custom Movie type. A fellow developer will use this method like:

This application goes live and suddenly users receive a null reference exception. Now what? Fellow developer decides to have a look ‘under the hood’:

Ah, so the method returns a null if record isn’t found in the database.

Let’s look at another signature of a method we’ve defined in our service, what does this tell you:

It promises that if you pass two string parameters, you will receive a bool type. It’s not clear what the bool represent e.g. does false mean exception or validation issue. We’ll need to look at the implementation once again:

Now we notice that this method could throw exceptions too, without knowing the implementation this will break the application at runtime. Also we discover that the bool represents a validation issue.

These examples are simplistic but highlighted few key points about these method signatures:

  • Method signatures did not convey their true input and output – they were dishonest.
  • In order to understand the input/output, we had to look into the implementation of the method – they were not well encapsulated.
  • Input and output were ambiguous, without implementation details their meaning was unclear – they were not semantically readable.
  • It is only at runtime that an invalid value is caught – the design is not defensive, allows a programmer to write code that could potentially fail.

How can we improve on this? I’ll provide one solution that I prefer – honest methods using domain abstractions as parameters and return value.

Solution

Making your method signatures as specific as possible make them easier to consume and less error-prone. One technique is to think in terms of concepts present in business domain for which we’re writing the application and then using custom types to pass data in and out of methods. Using custom instead of primitive types provide richer semantic meaning to method signature and thus help with readability of the code.

Back to our examples, let’s say that the development team creates a custom type to be used for the application which will contain either the return value or the error message. They name it Result. Let’s look at modified method signature from our earlier example, what does this tell you:

It promises that if you pass int, you will receive a custom Result type, containing either the Movie or Error type. The caller should now ‘expect’ that something can go wrong when calling the method, it’s clear from the signature. An honest function always does what its signature says, and given an input of the expected type, it yields an output of the expected type—no Exceptions, no nulls.

FAQ: What if developer using the method doesn’t know what Result is? This would be part of team coding standards/guidelines. Then it is no different than using built-in types like int, float, enum etc. Developers know what to store in them. The custom type here has a clear semantic meaning, it would hold return value or a failure message.

Our next example is little more interesting, let’s look at modified method signature, what does this tell you:

It promises that if you pass custom type UploadFile, you will receive a custom type UploadImageResult. Perhaps this is not so clear to our fellow developer, but at least he can’t ‘accidently’ misuse it. However, this doesn’t necessarily require him to know the implementation of this method. He only needs to know how to create these custom data types.

To make it even more interesting, let’s say he/she doesn’t even have access to source code for this method. He decides to use intellisence:

  • Intellisence for service:

  • Service needs UploadFile, intellisence for it:

  • Service returns UploadImageResult, intellisence for it:

The public API for the method is discoverable and makes it difficult for the developer using it to get it wrong. We still have the guard clauses and validation, however, these are encapsulated within the UploadFile type:

FAQ: UploadFile still require string parameters, are we not just moving the problem of primitive types and parameter guard clauses to a different place? Yes we are. We are moving it up the call stack. We ‘do’ need these checks, but now they are a) part of object creation b) encapsulated in a type c) help with ‘failing fast’.

FAQ: Why avoid throwing exceptions in a method and instead advertise failure using return type? By throwing exception you’re breaking the application and making an assumption that caller will catch it. You’re also coupling the caller with implementation detail of a method, breaking its encapsulation. Caller should depend only on input and output of a method, exceptions are neither of them.

When designing the public interface of our classes and methods, we should try to avoid it’s misuse. Creating methods with honest signatures is a good technique to hide implementation details (encapsulation), reduce bugs and improve code readability.

Note: this is not the only way to design your code, even if you don’t agree with my solution, hopefully it will add a technique to your repertoire.

Source Code

GitHub: https://github.com/TahirNaushad/Fiver.Design.HonestMethods

Leave a Reply