Your Simple Coding Guide: Functions and Methods
Let's explore four of the most important factors that create solid functions and methods in this code walkthrough.
Join the DZone community and get the full member experience.Join For Free
What makes a good function or method? It's not just one thing, but rather a combination of things where each is significant. If something is flawed, it affects the whole function. So what are these common slip-ups, and how can you avoid them?
It Has a Meaningful Name
Functions should have names that describes their purpose or functionality. When a function has a meaningful name, it's easy to read and understand its purpose.
For example, if a function's purpose is to find a customer by ID, a good name could be findCustomerById(id: String). It could also just as well be findCustomer(id: String) because the function signature implies that the customer is found by their ID. The word find also implies that the customer might be found or it might not be found.
If the function's name would be changed to getCustomer(id: String), its meaning changes because now it implies that there's no fallback; the customer is either found or the function fails miserably and maybe throws an exception.
Both are valid names for a function but they have a different meaning and therefore their implementations should also be different.
It Has As Few Parameters As Possible
Let's take in example a function that has identical behavior but differing signatures:
fun addCustomer( firstname: String, lastname: String, streetAddress: String, city: String, zipCode: String )
data class Address( val street: String, val city: String, val zipCode: String, val streetNumber: String ) data class Customer( val firstname: String, val lastname: String, val address: Address ) fun addCustomer(customer: Customer)
It Does What's Expected
A function should do what's expected of it. Nothing more, nothing less. If a function is named as findAddress(latitude, longitude), it should find the address in the given coordinates, or if no address can be translated for the coordinates, return a None, null, Empty, or what ever is the appropriate type for the given language. The function should not do anything else, like find adjacent addresses or building records of the coordinates. The function can have side effects like logging or analytics, but those are invisible to the input and to the output.
Functions should be designed so they're testable. In the previous code sample, I defined the function addCustomer but didn't define any return type so that it's testability is questionable. Sure, it could be tested with mocks or spies depending on what the internal implementation is like, but by just simply giving it a return type, we can easily test it:
fun addCustomer(customer: Customer): Customer
With the given function signature, we can return the added customer entity to the callee. With that addition, we can also test that the function does what it's supposed to do with that customer entity (i.e., assign it a unique id.)
In the next installment of this article series, I'll be writing about contracts and how they're related to code. Stay tuned!
Published at DZone with permission of Jori Lytter, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.