Naming Things
· 7 minNaming things might be hard, but consistently naming similar things should not be! Years ago I started using a list of method name prefixes with rules on when to use them. Since then I’ve introduced it successfully within 2 teams to make collaboration easier. These are most applicable for OO, but not exclusively.
🙋 Why is readability so important
It’s been known for a while that a lot of a developers time goes to understanding the code worked on. According to this comprehensive study 60-80% of time is spend understanding code. You can find a more thorough explanation here.
This leaves a lot of room for optimization!
🔑 Consistency is key
When every developer on a team uses the same patterns to name their methods, variables and classes it becomes much easier for the next person to get up to speed. Not only when naming their own things, but more importantly when trying to understand what was written before.
🧅 Encapsulation at the cost of understanding
We are taught to encapsulate our data in our models by exposing behaviour, this often ends up with our classes having lots of getters even though under the hood a lot more is going on than simply “getting” some data. An argument can be (and has been) made that it should not matter how a class is getting this data, but when you have to debug or change such behaviour it often does help to have a little more hints as to what is going on under the hood. Especially when using a classical layered architecture and multiple services call methods with the same name in an ever deeper layer, it becomes really hard to grasp why each layer exists.
📛 Method name patterns
This paragraph lists method prefixes and rules around when they should be used. These prefixes should be followed by a logical explanation of what the method does. Note that it is almost always better to have a longer method name when it can avoid even 1 developer misunderstanding or having to figure it out from the implementation.
Get
Basic getter. These methods are meant to get data out of objects, this data should in most cases directly relate to a field of the object, however, in some situations it might be required to calculate/determine some value based on other data of the object and to expose it for serialization purposes. Also lazy initialization is allowed. But avoid more complex behaviour or things that can cause exceptions.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 0 | value | ❌ |
public Something getSomething() {
return something;
}
// lazy initialization
public List<Something> getSomethings() {
if (somethings == null) {
somethings = new ArrayList();
}
return somethings
}
Set
Basic setter. These methods are meant to set fields on an object. Avoid any complex behaviour or possible exceptions.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 1 | void | ❌ |
public void setSomething(Something something) {
this.something = something;
}
With
Called a wither. It can be used to set a field on an object, or create a new immutable object copy with the specific field set to the given value. Returning the object is meant for chaining method calls.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 1 | this | ❌ |
// immutable
public Parent withSomething(Something something) {
return this.toBuilder().something(something).build();
}
// mutable
public Parent withSomething(Something something) {
this.something = something;
return this;
}
Of
Static constructor defined on the return type class. It’s meant to transform (or build up from) many things into a new instance of an object.
Static | Args | Returns | Throws |
---|---|---|---|
✔️ | 1-n | instance | input validation |
public class Parent {
public static Parent of(Something a, Other b) {
return Parent.builder().a(a).b(b).build();
}
}
From
Special static constructor defined on the return type class. It’s meant to transform one one POJO into another.
Static | Args | Returns | Throws |
---|---|---|---|
✔️ | 1-n | instance | input validation |
public class Parent {
public static Parent fromSomething(Something something {
return Parent.builder().something(something).build();
}
public static Parent of(Something a, Other b) {
return Parent.builder().a(a).b(b).build();
}
}
To
Instance transformation method to transform a POJO into another POJO.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 0 | instance | input validation |
public class Parent {
String name;
public Something toSomething() {
return Something.builder().parentName(name).build();
}
}
Retrieve
Method for retrieving something from another place. This can be another internal service, an external service or a database.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 0-n | object or (Possibly empty) collection | input validation or object not found. Don’t throw on empty collection |
// collection
public Collection<Thing> retrieveThingsForCustomer(Customer customer) {
returns db.findAllByCustomerId(customer.getId());
}
// single
public Thing retrieveThingById(String id) {
retruns db.findOne(id)
.orElseThrow(() -> new NotFoundException("Thing not found by id " + id));
}
Find
Method for optionally retrieving something from another place. This can be another internal service, an external service or a database.
Static | Args | Returns | Throws |
---|---|---|---|
❌ | 0-n | Optional | input validation or technical issues. Don’t throw on object not found |
public Optional<Thing> findThingById(String id) {
retruns db.findOne(id);
}
Determine
Service, model or util method executing some business logic to determine an outcome. Useful on model classes in stead of getters for non existing fields, but also for more complex logic.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 0-n | value or Object | input validation or technical issues |
public Instant determineExpiresAt() {
return Instant.now(clock).plusSeconds(EXPIRE_IN_SECONDS);
}
Calculate
Service, model or util method executing some business logic to calculate an outcome. This is a more specific form of Determine that it should not do any I/O and only use CPU time.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 0-n | value or Object | input validation |
public class Rectangle {
float width;
float height;
public float calculateArea() {
return width * height;
}
}
Generate
Generate a new object from internal data, never by external source. Usually without arguments, but there can be exceptions to steer the generation process. Acts like a constructor, but is usually defined in a service or helper.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 0(-n) | Object | input validation |
public static Fixture generateFixture() {
retrurn Fixture.builder().value(1).build();
}
Create
Create something new from (partial) data in internal or external source like via the API of another service or in a database. Acts like a constructor or builder, but is defined in a service or helper.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 1-n | value or Object | input validation |
public static Fixture createFixture(String variable) {
retrurn Fixture.builder().value(1).variable(variable).build();
}
Check
Validate business rule and return true/false. Usually on an entity or util class
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 0-n | boolean | ❌ |
public class Order {
Collection<Items> items;
boolean checkAllItemsAvailable() {
return items.stream().allMatch(Item::isAvailable);
}
}
Validate
Validate business rule and throws if invalid. Usually on an entity or util class. Combines well with a check method.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 0-n | void | if invalid |
public class Order {
Collection<Items> items;
boolean checkAllItemsAvailable() {
return items.stream().allMatch(Item::isAvailable);
}
boolean validateAllItemsAvailable() {
if(!checkAllItemsAvailable()) {
throw new ItemAvailabilityException();
}
}
}
Keep
Filters collection and keeps only items that meet certain condition.
Static | Args | Returns | Throws |
---|---|---|---|
🤷 | 1-n | Collection | ❌ |
private static Collection<Item> keepItemsWithColor(Collection<Item> items, Color color) {
return items.stream()
.filter(item -> color.equals(item.getColor()))
.toList();
}
☘️ Field name patterns
Field names are a lot harder to standardize on, but there are a few categories where it is easy to achieve.
Time
A timestamp should be identified by the suffix _at
or At
:
Instant createdAt;
Instant updatedAt;
Prefer updated
over modified
.
Prefer created
over started
.
An exception is when the timestamp does not have a predefined meaning. Then we will fall back to timestamp
.
Changed by
A change author should be identified by a field with a _by
or By
suffix:
String createdBy;
Author updatedBy;
Depending on your usecase this could either be a simple String or an object containing more data.
State
You might want to track the state of a process. I like to use a state history so it is easy to track state transitions over time:
{
"stateHistory" : [
{
"status": "CREATED",
"timestamp" : "2022-08-24T10:11:12.000Z"
},
{
"status": "QUEUED",
"timestamp" : "2022-08-24T10:11:12.100Z"
},
{
"status": "IN_PROGRESS",
"timestamp" : "2022-08-24T10:11:12.200Z"
},
{
"status": "FAILED",
"timestamp" : "2022-08-24T10:11:12.250Z"
"message" : "500 internal server error"
},
{
"status": "RETRIED",
"timestamp" : "2022-08-24T10:11:12.300Z"
"message" : "Retrying 1/3 times"
},
{
"status": "IN_PROGRESS",
"timestamp" : "2022-08-24T10:11:12.400Z"
},
{
"status": "SUCCESSFUL",
"timestamp" : "2022-08-24T10:11:12.500Z"
}
]
}
💡 Conclusion
If you can convince your team to settle on any convention for method names you are already winning. Feel free to use these, or come up with your own!