Dada
|> Structures
|> Algorithms

How to Design a Function

A short template on how documenting and testing help writing code

This is a small template that teaches you how to write a function. This template distills a number of great practices into a brief checklist. This short checklist, which is the core teaching of How To Design Programs book, concentrates writing code, documentation, tests, and thinking about your function's input range in one small checklist.

The book uses Racket as a teaching language. I am going to give an example using Ruby.

The checklist

  1. Write the name of the function
  2. Write a one-sentence comment describing what the function does
  3. Write the type signature
  4. Write an example of use in the comments
  5. Write tests a. It proves that it works b. It explores the boundary cases, i.e. range, incorrect types, empty lists, etc.
  6. Write the code

Now let's walk through a business software example. Let's say you want to insert a new blog record from an http request.

  1. Write the name

We write the function with its name. Nothing more. Notice I am not writing the body or the parameters. Only write the name so that it is syntactically correct in the language that you are working on.

def create_post()
end
  1. Write a one-sentence comment describing the function

This comment documents what our intent for the function is. When designing a function, it helps us focus on what we are trying to write. If you can't express it in words, you can't express it in code.

# creates new post
def create_post()
end
  1. Write the type signature

Even if you work with a dynamic language you are working with types. The point of this step is to explicitly think about what is your input and what will be your output. Statically typed languages will enforce your type signature for you. If you are using a dynamic language, you need this information to know what is supposed to be happening.

The book uses the following type format. You make a list of each parameter followed by an arrow (->), ending with the return type. This will be familiar to people who have studied Haskell or OCaml. If the return is void, you can say "void" or "unit".

# creates new post
# Hash -> Unit  
def create_post()
end

This is an example using more idiomatic Ruby.

# creates new post
# @param params, Hash, the http parameters hash 
# @return void 
def create_post(params)
end
  1. Write an example

Writing an example of how to use your function helps you further design it. It is also one of the most helpful documentation you can add. Most people will often google for examples. Include them.

# creates new post
# @param blog_parameters, Hash, the http parameters hash 
# @return void 
# Example: create_post(params[:blog])
def create_post(blog_params)
end

Notice how I changed the name of the parameter. I did this so that the example would be less confusing since I wanted to pass 'params[:blog]'. Thanks to writing the example, now I have a more explicit name for my function signature.

  1. Write tests

This is a two part step. We are asked to write tests for the correct output and for input ranges. So the happy path and the boundary checks.

Let's start with basic correctness when everything is right, the happy path. # creates new post # @param blog_parameters, Hash, the http parameters hash # @return void # Example: create_post(params[:blog]) def create_post(blog_parameters) end

# testing correct behavior or happy path
# The test is ruby pseudo code
def test_creation()
  # setup
  params = {title: "hello", body: "hello world!", author: "Joe Smith" }

  # exercise
  create_post(params)

  # verification
  record = ORM.find_by(title: "hello") 
  assert !record.nil? 
end

This made us discover that we we have three required parameters. Now we can change our function again with this new information.

# creates new post
# @param title, String, title of the blog post 
# @param body, String, body of the blog post 
# @param author, String, body of the blog post 
# @return void 
# Example: create_post(title, body, author)
def create_post(title, body, author)
end

Now we can think about input violation and ranges. How short can a title be? And how short can a body be? What should happen we send a null value for author? What if the inputs are too big? Is it okay to have no author?

Thinking in term of ranges helps us identify the right number of tests. This is a superior way thinking about it than old advice I was told for TDD where you kept coming up with tests until you couldn't think of any more.

  1. Write the body of the function

Now we are ready to write the body of the function. The process has made it clear what we need to do. Now we can execute.

# creates new post
# @param title, String, title of the blog post 
# @param body, String, body of the blog post 
# @param author, String, body of the blog post 
# @return void 
# Example: create_post(title, body, author)
def create_post(title, body, author)
  blog = Blog.new(title, body, author) 
  ORM.save(blog)
end

In the book they have created a whole library of templates for the find of work it is needed to do. Some functions require looping, so they have template looping code. If you have time, explore that library. It most likely will speed up coding if you think in those terms.