Mark Seemann helps programmers make source code easier to maintain. In this excerpt from his book, he provides guidance on naming methods in API design to avoid team confusion including x'ing out the name.
Just as comments can grow stale and misleading over time, so can method names. Hopefully you pay more attention to method names than comments, but it still happens that someone changes the implementation of a method but forgets to update the name.
Fortunately, with a statically typed language, you can use types to keep you honest. Design APIs so that they advertise their contracts with types. Consider the updated version ofIReservationsRepositoryshown in the listing below. It has a third method namedReadReservation。It's a descriptive name, but is it sufficiently self-documenting?
One question I often find myself asking when I explore an unfamiliar API is:Should I check the return value for null?How do you communicate that in a durable and consistent way?
public interfaceIReservationsRepository
{
TaskCreate(Reservation reservation);
Task
Task
}
Listing:IReservationsRepositorywith an additionalReadReservationmethod compared to Restaurant/ee3c786/Restaurant.RestApi/IReservationsRepository.cs
You could try to communicate with descriptive naming. For example, you might call the methodGetReservationOrNull。This works, but is vulnerable to changes in behaviour. You might later decide to change the API design so that null is no longer a valid return value, but forget to change the name.
Notice, however, that with C#'s nullable reference types feature, that information is already included in the method's type signature. Its return type isTask
As an exercise in API design, try tox out the method namesand see if you can still figure out what they do:
public interfaceIReservationsRepository
{
TaskXxx(Reservation reservation);
Task
Task
}
What does it look likeTask Xxx(Reservation reservation)does? It takes a Reservation object as input, but it doesn't return anything1。因为没有返回值,must perform some sort of side effect. What might it be?
It could be that it saves the reservation. It might also conceivably transform it to an email and send it. It could log the information. This is where the defining object comes into play. When you know that the object that defines the method is calledIReservationsRepository, the implied context is one of persistence. This enables you to eliminate logging and emailing as alternatives.
Still, it's not clear whether that method creates a new row in the database, or it updates an existing. It might even do both. It's also technically possible that it deletes a row, although a better candidate signature for a delete operation would beTask Xxx(Guid id)。
What aboutTask
Finally,Task
This technique works as long as objects afford only few interactions. The example has only three members, and they all have different types. When you combine method signatures with the name of the class or interface, you can often guess what a method does.
Notice, though, how it took more guesswork to reason about the anonymizedCreatemethod. Since there's effectively no return type, you have to reason about its intent based exclusively on the input type. With the queries, you have both input types and output types to hint at the method's intent.
X'ing out method names can be a useful exercise, because it helps you empathise with future readers of your code. You may think that the method name you just coined is descriptive and helpful, but it may not be to someone with a different context.
Names are still helpful, but you don't have to repeat what the types already state. This gives you room to tell the reader something he or she can't divine from the types.
Notice the importance of keeping the tools sharp, so to speak. This is another reason to favour specialised APIs over Swiss Army knifes. When an object only exposes three or four methods, each method tends to have a type distinct from the other methods in that context. When you have dozens of methods on the same object, this is less likely to work well.
The method types are most likely to be helpful when the types alone disambiguate them from each other. If all methods returnstringorint, their types are less likely to be helpful. That's another reason to eschew stringly typed APIs.
1Strictly speaking, it returns aTask, but that object contains no additional data. RegardTaskas the asynchronous equivalent ofvoid。