Click here to Skip to main content
15,892,674 members
Articles / All Topics

Design Tip – Write Honest Methods

Rate me:
Please Sign up or sign in to vote.
4.53/5 (4 votes)
13 May 2018CPOL5 min read 5.9K   5   2
When designing the public interface of our classes and methods, we should try to avoid its misuse. Creating methods with honest signatures is a good technique to hide implementation details, reduce bugs and improve code readability. Continue reading...

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:

  1. parameters
  2. return value

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

C#
public Movie FindById(int id) { ... }

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

C#
var movie = FindById(5);            
// use movie

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

C#
public Movie FindById(int id)
        {
            if id not found
                return null;

            return new Movie(); // id found
        }

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:

C#
public bool UploadDocument(string fileName, string fileExtension) { ... }

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

C#
public bool UploadDocument(string fileName, string fileExtension)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentException("file name empty");
            if (string.IsNullOrEmpty(fileExtension))
                throw new ArgumentException("file extension empty");

            if (fileExtension == "pdf" || 
                 fileExtension == "doc" || 
                 fileExtension == "docx")
            {
                // upload

                return true;
            }
            else
            {
                return false;
            }
        }

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:

C#
public Result<Movie, Error> FindById(int id) { ... }

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 a little more interesting, let’s look at modified method signature, what does this tell you:

C#
public UploadImageResult UploadDocument(UploadFile file) { ... }

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 ‘accidentally’ 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, intellisense for it:

  • Service returns UploadImageResult, intellisense 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:

C#
public sealed class UploadFile
    {
        public UploadFile(string fileName, string fileExtension)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentException("file name empty");
            if (string.IsNullOrEmpty(fileExtension))
                throw new ArgumentException("file extension empty");
            if (!IsValidFormat(fileExtension))
                throw new ArgumentException("incorrect file format");

            FileName = fileName;
            FileExtension = fileExtension;
        }

        public string FileName { get; }
        public string FileExtension { get; }
        private bool IsValidFormat(string fileExtension)
            => (new[] { "pdf", "doc", "docx" }).Contains(fileExtension)
    }

FAQ: UploadFile still requires 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 its 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
SuggestionIs there any special reason for using ArgumentException instead of ArgumentNullException? Pin
Pankaj Nikam13-May-18 19:52
professionalPankaj Nikam13-May-18 19:52 
GeneralRe: Is there any special reason for using ArgumentException instead of ArgumentNullException? Pin
User 104326419-May-18 3:31
User 104326419-May-18 3:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.