DEV Community

Cover image for 27 Tips for making your code read like beautifully written prose
Petri Silen
Petri Silen

Posted on

27 Tips for making your code read like beautifully written prose

Why is it important how you name software entities like functions, variables and classes? The reason is to make the code understandable for other developers and your future self. How many times have you read some code written by yourself a couple of years ago and thought what the heck is this code doing? We should remember that code is more often read than written, so it is crucial to ensure that code reads well. Don’t try to make your code too concise using too short names. Use the shortest most descriptive names. When code is written using great names, it makes the code read like beautifully written prose. Back in the days when I was a junior programmer, I did not care much about names in code. But nowadays I care about names in code a lot. I am always asking myself, is this name good enough or how could I improve some names. When you use great names in code, you don’t need to write so many comments. In the next sections, I am going to present some useful naming conventions for functions, variables, interfaces and classes. The naming conventions described below are not something I just quickly came up with but they are a product of practicing programming for almost 30 years. You can also find the below information in my book 📕Clean Code Principles And Patterns: A Software Practitioner’s Handbook 📕.

Naming Functions

Functions should do one thing, and the name of a function should describe what the function does. The function name should contain a verb that indicates what the function does. The function name should usually start with a verb, but exceptions exist. If a function returns a value, and if possible, try to name the function so that the function name describes what it returns.

Tip #1: Name the function on correct abstraction level

Let’s have an example with the following code:

void deletePageAndAllReferences(Page page) {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the function seems to do two things: deleting a page and removing all the references to that page. But if we look at the code inside the function, we can realize that it is doing a third thing also: deleting a page key from configuration keys. So should the function be named deletePageAndAllReferencesAndConfigKey? It does not sound reasonable. The problem with the function name is that it is at the same level of abstraction as the function statements. The function name should be at a higher level of abstraction than the statements inside the function.

How should we then name the function? We could name the function just delete. This would tell the function caller that a page will be deleted. The caller does not need to know all the actions related to deleting a page. The caller just wants a page to be deleted. The function implementation should fulfill that request and do the needed housekeeping actions, like removing all the references to the page being deleted and so on.

So, if you spot a function name containing and word, your function is either doing multiple things or is not named at the correct level of abstraction. You should have your functions doing a single thing only.

Tip #2: Don’t repeat the interface/class name in the function name

Below is an example of an interface containing two methods named with simple verbs only. It is not necessary to name the methods as startThread and stopThread because the methods are already part of the Thread interface, and it is self-evident what the start method starts and what the end method ends.

public interface Thread {
  void start();
  void stop();
}
Enter fullscreen mode Exit fullscreen mode

Tip #3: Don’t lie in the function name

Many languages offer streams that can be written to, like the standard output stream. Streams are usually buffered, and the actual writing to the stream does not happen immediately. For example, the below statement does not necessarily write to the standard output stream immediately. It buffers the text to be written later when the buffer is flushed to the stream. This can happen when the buffer is full, when some time has elapsed since the last flush or when the stream is closed.

stdOutStream.write(...);
Enter fullscreen mode Exit fullscreen mode

The above statement is misleading and could be corrected by renaming the function to describe what it actually does:

stdOutStream.writeOnFlush(...);
Enter fullscreen mode Exit fullscreen mode

Let’s have another example:

grpcChannel.shutdown().awaitTermination(30, TimeUnit.SECONDS);
Enter fullscreen mode Exit fullscreen mode

In the above code, the shutdown method does not shut the channel down. It actually only requests a shutdown. We should also use a single term shutdown instead of using two terms for the same thing: shutdown and termination. Below is the above code refactored:

grpcChannel.requestShutdown().awaitShutdown(30, TimeUnit.SECONDS);
Enter fullscreen mode Exit fullscreen mode

Tip #4: Name a throwing function with a try prefix

It is easier not to forget handling thrown errors when a throwing function is named so that it clearly indicates it can throw:

public interface ConfigParser {
  // Nothing in the function signature tells it can throw
  Configuration parseConfig(final String configJson);
}

public interface ConfigParser {
  // Try-prefix tells that the function can throw
  Configuration tryParseConfig(final String configJson);
}
Enter fullscreen mode Exit fullscreen mode

Tip #5: Don’t repeat the function target in the function name

The below example is repeating the function target in the function name:

public interface ConfigParser {
  // Not optimal naming
  Configuration tryParseConfig(final String configJson);
}
Enter fullscreen mode Exit fullscreen mode

We can remove the function target Config from the function name:

public interface ConfigParser {
  // Better naming
  Configuration tryParse(final String configJson);
}
Enter fullscreen mode Exit fullscreen mode

When we use the latter function name somewhere in code, it makes the code read better:

// Word "config" repeated, not optimal
final var config = configParser.tryParseConfig(configJson);

// Shorter and reads better
final var config = configParser.tryParse(configJson);
Enter fullscreen mode Exit fullscreen mode

Tip #6: Use a preposition in the function name only when necessary

You don’t need to add a preposition to a function name if the preposition can be assumed (i.e., the preposition is implicit). In many cases, only one preposition can be assumed. If you have a function named wait, the preposition for can be assumed, and if you have a function named subscribe, the preposition to can be assumed. You don't need to use function names waitFor and subscribeTo.

Suppose a function is named laugh(person: Person). Now we have to add a preposition because none can be assumed. We should name the function either laughWith(person: Person) or laughAt(person: Person).

Tip #7: Name method pairs logically

Methods in a class can come in pairs. A typical example is a pair of getter and setter methods. When you define a method pair in a class, name the methods logically. The methods in a method pair often do two opposite things, like getting or setting a value. If you are unsure how to name one of the methods, try to find an antonym for a word. For example, if you have a method whose name starts with “create” and are unsure how to name the method for the opposite action, try a Google search: “create antonym”.

Examples of method pairs:

  • add/remove
  • insert/delete
  • start/stop

Tip #8: Name predicate functions to read as boolean statements

The naming of boolean functions (predicates) should be such that when reading the function call statement, it reads as a boolean statement that can be true or false.

The naming of boolean functions should be such that when reading the function call statement, it makes a statement that can be true or false. Below are some examples:

public class String {
  public boolean isEmpty() { 
    //... 
  }

  public boolean startsWith(final String anotherString) { 
    //...
  }
}

final String line = fileReader.readLine();

// Here we have a statement: line is empty? True or false?
if (line.isEmpty()) {
  // ...  
}

// Here we have statement: line starts with a space [character]?
// true or false?
if (line.startsWith(" ")) { 
    // ...
}

public class Thread {
  public boolean shouldTerminate() {
    // ...
  }

  public boolean isPaused() {
    // ...
  }

  public boolean canResumeExecution() {
    // ...
  }

  public void run() {
    // ...

    // Here we have statement: if [this] should terminate?
    // True or false?
    if (shouldTerminate()) { 
      return;
    }

    // Here we have statement: if [this] is paused and
    // [this] can resume execution? True or false? 
    if (isPaused() && canResumeExecution()) { 
      // ...
    }

    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

A boolean returning function is correctly named when you call the function in code and can read that function call statement in plain English. Below is an example of incorrect and correct naming:

public class Thread {
  public boolean stopped() { // Incorrect naming
    // ...
  } 

  public boolean isStopped() { // Correct naming
    // ...
  }
}

if (thread.stopped()) { 
  // Here we have: if thread stopped
  // This is not a statement with a true or false answer
  // It is the second conditional form, 
  // asking what would happen if the thread stopped.
  // ...
}

if (thread.isStopped()) { 
  // Here we have statement: if thread is stopped
  // True or false?
  // ...   
}
Enter fullscreen mode Exit fullscreen mode

Tip #9: Sometimes a function can be named with an implicit verb

Factory method names usually start with the verb create. Factory methods can be named so that the create verb is implicit, for example:

Optional.of(final T value)
Optional.empty() // Not optimal, 'empty' can be confused as a verb
Either.withLeft(final L value)
Either.withRight(final R value)
SalesItem.from(final SalesItemArg salesItemArg)
Enter fullscreen mode Exit fullscreen mode

Similarly, conversion methods can be named so that the convert verb is implicit. Conversion methods without a verb usually start with the to preposition, for example:

value.toString();
object.toJson();
Enter fullscreen mode Exit fullscreen mode

Tip #10: Naming a getter function without the respective setter function

Property getter functions are usually named get + <property-name>. It is also possible to name a property getter that does not have a respective setter using just the property name. This is acceptable in cases where the property name cannot be confused with a verb. Below is an example of property getters:

final var list = new MyList();

list.size(); // OK
list.length(); // OK
list.empty(); // NOT OK, empty can be a verb. 
list.isEmpty(); // OK
Enter fullscreen mode Exit fullscreen mode

Tip #11: Naming lifecycle functions

Lifecycle methods are called on certain occasions only. Lifecycle method names should answer the question: When or “on what occasion” will this method be called? Examples of good names for lifecycle methods are: onInit, onError, onSuccess, afterMount, beforeUnmount. In React, there are lifecycle methods in class components called componentDidMount, componentDidUpdate and componentWillUnmount. There is no reason to repeat the class name (component) in the lifecycle method names. Better names would be: afterMount, afterUpdate, and beforeUnmount.

Naming Variables

A good variable name should describe the variable’s purpose and its type.

Naming variables with names that also convey information about the variable’s type is crucial in untyped languages and beneficial in typed languages, too, because modern typed languages use automatic type deduction, and you won’t always see the actual type of a variable. But when the variable name tells its type, it does not matter if the type name is not visible.

Tip #12: Naming integer variables

Some variables are intrinsically integers, like age or year. Everybody understands immediately that the type of an age or year variable is a number and, to be more specific, an integer. So you don’t have to add anything to the variable’s name to indicate its type. It already tells you its type.

One of the most used categories of integer variables is a count or number of something. You see those kinds of variables in every piece of code. I recommend using the following convention for naming those variables: numberOf<something> or alternatively <something>Count. For example, numberOfFailures or failureCount. You should not use a variable name failures to designate a failure count. The problem with that variable name is it does not clearly specify the type of the variable and thus can cause some confusion. This is because a variable named failures can be misunderstood as a collection variable (e.g., a list of failures).

If the unit of a variable is not self-evident, always add information about the unit to the end of the variable name. For example, instead of naming a variable tooltipShowDelay, you should name it tooltipShowDelayInMillis or tooltipShowDelayInMillisecs. If you have a variable whose unit is self-evident, unit information is not needed. So, there is no need to name an age variable as ageInYears. But if you are measuring age in months, you must name the respective variable as ageInMonths so that people don’t assume that age is measured in years.

Tip #13: Naming floating-point number variables

If you need to store an amount of something that is not an integer, use a variable named <something>Amount, like rainfallAmount. When you see “amount of something” in code, you can automatically think it is a floating-point number. If you need to use a number in arithmetic, depending on the application, you might want to use either floating-point or integer arithmetic. In the case of money, you should use integer arithmetic to avoid rounding errors. Instead of a floating-point moneyAmount variable, you should have an integer variable, like moneyInCents, for example.

If the unit of a variable is not self-evident, add information about the unit to the end of the variable name, like rainfallAmountInMillimeters, widthInInches, angleInDegrees (values 0–360), failurePercent (values 0–100), or failureRatio (values 0–1).

Tip #14: Naming boolean variables

Boolean variables can have only one of two values: true or false. The name of a boolean variable should form a statement where the answer is true or false, or yes or no. Typical boolean variable naming patterns are: is<something>, has<something>, did<something>, should<something>, can<something>, or will<something>. Some examples of variable names following the above patterns are isDisabled, hasErrors, didUpdate, shouldUpdate, and willUpdate.

The verb in the boolean variable name does not have to be at the beginning. It can and should be in the middle if it makes the code read better. Boolean variables are often used in if-statements where changing the word order in the variable name can make the code read better.

Below is a code snippet where we have a boolean variable named isPoolFull:

if (const bool isPoolFull = m_pooledMessages.size() >= 200U;
      isPoolFull)
{
  // ...
Enter fullscreen mode Exit fullscreen mode

We can change the variable name to poolIsFull to make the if-statement read more fluently. In the below example, the if-statements reads if poolIsFull instead of if isPoolFull:

if (const bool poolIsFull = m_pooledMessages.size() >= 200U;
      poolIsFull)
{
  // ...
Enter fullscreen mode Exit fullscreen mode

Don’t use boolean variable names in the form of <passive-verb>Something, like insertedField, because this can confuse the reader. It is unclear if the variable name is a noun that names an object or a boolean statement. Instead, use either didInsertField or fieldWasInserted.

Below is a Go language example of the incorrect naming of a variable used to store a function return value. Someone might think that tablesDropped means a list of dropped table names. So, the name of the variable is obscure and should be changed.

tablesDropped := dropRedundantTables(prefix,
                                     vmsdata,
                                     cfg.HiveDatabase,
                                     hiveClient,
                                     logger)
if tablesDropped {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Below is the above example modified so that the variable name is changed to indicate a boolean statement:

tablesWereDropped := dropRedundantTables(prefix,
                                         vmsdata,
                                         cfg.HiveDatabase,
                                         hiveClient,
                                         logger)
if tablesWereDropped {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Tip #15: Naming strings containing a number

When you need to store numerical data in a string variable, tell the code reader clearly that it is a question about a number in string format, and use a variable name in the following format: <someValue>String or <someValue>AsString. It makes the code more prominent and easier to understand. For example:

const year = parseInt(yearAsString, 10);
Enter fullscreen mode Exit fullscreen mode

Tip #16: Naming collection (array, list, and set) variables

When naming arrays, lists, and sets, you should use the plural form of a noun, like customers, errors, or tasks. In most cases, this is enough because you don’t necessarily need to know the underlying collection implementation. Using this naming convention allows you to change the type of a collection variable without needing to change the variable name. If you are iterating over a collection, it does not matter if it is an array, list, or set. Thus, it does not bring any benefit if you add the collection type name to the variable name, for example, customerList or taskSet. Those names are just longer. You might want to specify the collection type in some special cases only. Then, you can use the following kind of variable names: queueOfTasks, stackOfCards, or orderedSetOfTimestamps.

Below is an example in Go language, where the function is named correctly to return a collection (of categories), but the variable receiving the return value is not named according to the collection variable naming convention:

vmsdata, error = vmsClient.GetCategories(vmsUrl, logger)
Enter fullscreen mode Exit fullscreen mode

Correct naming would be:

vmsCategories, error = vmsClient.GetCategories(vmsUrl, logger)
Enter fullscreen mode Exit fullscreen mode

Tip #17: Naming map variables

Maps are accessed by requesting a value for a certain key. This is why I recommend naming maps using the pattern keyToValueMap. Let’s say we have a map containing order counts for customer ids. This map should be named customerIdToOrderCountMap. Or if we have a list of suppliers for product names, the map variable should be named productNameToSuppliersMap. Below is an example of accessing maps in Java:

final var orderCount = customerIdToOrderCountMap.get(customerId);
final var suppliers = productNameToSuppliersMap.get(productName);
Enter fullscreen mode Exit fullscreen mode

Below is an example of iterating over a map in JavaScript:

Object.entries(customerIdToOrderCountMap)
      .map(([customerId, orderCount]) => ...);
Enter fullscreen mode Exit fullscreen mode

Tip #18: Naming pair and tuple variables

A variable containing a pair should be named using the pattern variable1AndVariable2. For example: heightAndWidth. And for tuples, the recommended naming pattern is variable1Variable2...andVariableN. For instance: heightWidthAndDepth.

Below is an example of using pairs and tuples in JavaScript:

const heightAndWidth = [100, 200];
const heightWidthAndDepth = [100, 200, 40];
const [height, , depth] = heightWidthAndDepth;
Enter fullscreen mode Exit fullscreen mode

Tip #19: Naming object variables

Object variables refer to an instance of a class. Class names are nouns written with the first letter capitalized, like Person, Account, or Task. Object variable names should contain the respective class name: a person object of the Person class, an account object of the Account class, etc. You can freely decorate the object’s name, for example, with an adjective: completedTask. It is important to include the class name or at least some significant part of it at the end of the variable name. Then looking at the end of the variable name tells what kind of object is in question.

Tip #20: Naming function variables (callbacks)

Callback functions are functions supplied to other functions to be called at some point. If a callback function returns a value, it can be named according to the returned value, but it should still contain a verb. If the callback function does not return a value, you should name the callback function like any other function: Indicating what the function does. Below are some examples of naming callbacks:

const doubledValue = value => 2 * value;
const squaredValue = value => value * value;
const valueIsEven = nbr => (nbr % 2) === 0;
const values = [1, 2, 3, 4, 5]
const doubledValues = values.map(doubledValue);
const squaredValues = values.map(squaredValue);
const evenValues = values.filter(valueIsEven);

const strings = [" string1", "string2 "];
const trimmedString = str => str.trim();
const trimmedStrings = strings.map(trimmedString);

const sumOfValues = (sum, value) => sum + value;
values.reduce(sumOfValues, 0);
Enter fullscreen mode Exit fullscreen mode

Tip #21: Naming class properties

Class properties (i.e., class attributes, fields, or member variables) should be named so that the class name is not repeated in the property names. Below is an example of incorrect naming:

public class Order {
  private long orderId;
  private OrderState orderState;
}
Enter fullscreen mode Exit fullscreen mode

Below is the above code with corrected names:

public class Order {
  private long id;
  private OrderState state;
}
Enter fullscreen mode Exit fullscreen mode

Tip #22: Use Short, Common Names

When picking a name for something, use the most common shortest name. If you have a function named relinquishSomething, consider a shorter and more common name for the function. You could rename the function to releaseSomething, for example. The word “release” is shorter and more common than the “relinquish” word. Use Google to search for word synonyms, e.g., “relinquish synonym”, to find the shortest and most common similar term.

Tip #23: Pick One Name And Use It Consistently

Let’s assume that you are building a data exporter microservice and you are currently using the following terms in the code: message, report, record and data. Instead of using four different terms to describe the same thing, you should pick just one term, like message, for example, and use it consistently throughout the microservice code.

Tip #24: Avoid Obscure Abbreviations

Many abbreviations are commonly used, like str for a string, num/nbr for a number, prop for a property, or val for a value. Most programmers use these, and I use them to make long names shorter. If a variable name is short, the full name should be used instead, like numberOfItems instead of nbrOfItems. Use abbreviations in cases where the variable name otherwise becomes too long. What I especially try to avoid is using uncommon abbreviations. For example, I would never abbreviate amount to amnt or discount to dscnt because I haven’t seen those abbreviations used much in real life.

Naming Interfaces, Classes and Subclasses

Tip #25: Naming interfaces

An interface represents a capability, abstract thing or abstract actor.

When an interface represents an abstract thing, name it according to that abstract thing. The name of an interface representing an abstract thing should be a noun, never a verb or adjective. For example, if you have a drawing application with various geometrical objects, name the geometrical object interface Shape. It is a simple abstract noun. The term Shape is abstract because we cannot create an instance of Shape because we don’t know what kind of shape it is. Names should always be the shortest, most descriptive ones. There is no reason to name the geometrical object interface as GeometricalObject or GeometricalShape, if we can simply use Shape. Other good examples of abstract names are Chart and Widget, for example.

When an interface represents an abstract actor, name it according to that abstract actor. The name of an interface representing an abstract actor should be a noun, never a verb or adjective. The name of an actor interface should be derived from the functionality it provides. For example, if there is a parseConfig method in the interface, the interface should be named ConfigParser, and if an interface has a validateObject method, the interface should be named ObjectValidator. Don’t use mismatching name combinations like a ConfigReader interface with a parseConfig method or an ObjectValidator interface with a validateData method. Mismatching name combinations confuse code readers.

When an interface represents a capability, name it according to that capability. Capability is something that objects of a concrete class implementing the interface are capable of doing. For example, objects of a class could be sortable, iterable, comparable, equitable, etc. Name the respective interfaces according to the capability: Sortable, Iterable, Comparable, and Equitable. The name of an interface representing a capability usually ends with able or ing.

Don’t name interfaces starting with an I. Instead, use an Impl postfix for the class name to distinguish the class from the implemented interface when needed. For example, name the class implementing the ConfigParser interface as ConfigParserImpl. Most of the time, you should be programming against interfaces (remember dependency inversion principle from SOLID principles), and if every interface has its name prefixed with an I, unnecessary noise is added to the code. Use the I prefix only if it is an established programming language convention.

Tip #26: Naming Classes

A class represents either an abstract or concrete thing or actor.

Some examples of class names representing a concrete thing are: Account, Order, RectangleShape, CircleShape, TextWidget, and ColumnChart.

An abstract class is a class you cannot create an instance of. When you have an abstract class, name it with an Abstract prefix, for example AbstractChart or AbstractXAxisChart.

If a class implements an interface, you should name the class so that the class name refines the implemented interface name. Suppose we have an InputMessage interface and we would like to introduce concrete input message types. We should name those concrete types using the following pattern: <class-purpose> + <interface-name>. For example, we could create a Kafka + InputMessage = KafkaInputMessage class that represents an input message consumed from a Kafka data source. Or for the Shape interface, we can introduce various concrete shape classes, like CircleShape, RectangleShape, etc. When using this naming convention, you can always see the implemented interface when looking at the end of the class name.

If you are using a design pattern, don’t add the design pattern name to the class name if it does not bring any real benefit. For example, suppose we have a DataStore interface, a DataStoreImpl class, and a class that is wrapping a DataStoreinstance and uses the proxy pattern to add caching functionality to the wrapped data store. We should not name the caching class CachingProxyDataStore or CachingDataStoreProxy. The word proxy does not add significant value, so the class can be named just CachingDataStore. The name CachingDataStore tells everything we need to know: a data store with caching functionality. Similarly, if we had a SqlStatementExecutor class in a 3rd party library and we would like to add logging functionality to the class’s executeSqlStatement method, we should use the decorator pattern. We should not name the decorated class DecoratedLoggingSqlStatementExecutor but just LoggingSqlStatementExecutor.

Tip #27: Naming Subclasses

The subclass name should refine the base class name. The following naming pattern should be used: <subclass-purpose> + <base-class-name>. Suppose we have the KafkaInputMessage class and we would like to introduce new input message types for different input message data formats, like Avro and JSON. We should name those new classes in the following way: Avro + KafkaInputMessage = AvroKafkaInputMessage and Json + KafkaInputMessage = JsonKafkaInputMessage. When this naming convention is used for a subclass, the whole inheritance hierarchy can be seen in the subclass name, for example: Avro + Kafka + InputMessage = AvroKafkaInputMessage reveals the following inheritance hierarchy:

public interface InputMessage {
  // ...
}

public class KafkaInputMessage implements InputMessage {
  // ...
}
public class AvroKafkaInputMessage extends KafkaInputMessage {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

The above-presented naming convention does not always work optimally, especially when the inheritance hierarchy is deep. Sometimes you can drop intermediate subclass names. For example, we should probably use the name ColumnChart instead of ColumnXAxisChart because everyone knows that a column chart is an x-axis chart.

public interface Chart {
  // ...
}

public abstract class AbstractChart implements Chart {
  // ...
}

public abstract class AbstractXAxisChart extends AbstractChart {
  // ...
}

public class ColumnChart extends AbstractXAxisChart {
  // ...
}

public class LineChart extends AbstractXAxisChart {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Let’s have one more example. Suppose we have the following class hierarchy:

public interface Car {
  void drive(
    Location start,
    Location destination
 );
}

public class CombustionEngineCar implements Car {
  public void drive(
    final Location start,
    final Location destination
  ) {
    // ...
  }
}

public class ElectricEngineCar implements Car {
  public void drive(
    final Location start,
    final Location destination
  ) {
    // ...
  }
}

public class ManualTransmissionCombustionEngineCar 
         extends CombustionEngineCar {
  public void drive(
    final Location start,
    final Location destination
  ) {
    // ...
  }
}

public class AutomaticTransmissionCombustionEngineCar
         extends CombustionEngineCar {
  public void drive(
    final Location start,
    final Location destination
  ) {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

If we wanted to add more car properties like two or four-wheel drive, the above class hierarchy would grow deeper and the class names would become ridiculously long, like FourWheelDriveAutomaticTransmissionCombustionEngineCar. Fortunately, the above example has the wrong object-oriented design. We are using class inheritance in a situation where we should use class composition instead because an engine and transmission are properties of a car, i.e., there is a has-a relationship between a car and an engine and transmission. The above code should be corrected to use composition instead of inheritance. In the below example, we have the correct object-oriented design and reasonable class names.

public interface Drivable {
  void drive(
    Location start, 
    Location destination
  );
}

public interface Engine {
  // Methods like start, stop ...
}

public class CombustionEngine implements Engine {
  // Methods like start, stop ...
}

public class ElectricEngine implements Engine {
  // Methods like start, stop ...
}

public interface Transmission {
  // Methods like changeGear ...
}

public class AutomaticTransmission implements Transmission {
  // Methods like changeGear ...
}

public class ManualTransmission implements Transmission {
  // Methods like changeGear ...
}

public class Car implements Drivable {
  private final Engine engine;
  private final Transmission transmission;

  public Car(
    final Engine engine,
    final Transmission transmission
  ) {
    this.engine = engine;
    this.transmission = transmission;
  }

  public void drive(
    final Location start,
    final Location destination
  ) {
    // To implement functionality, delegate to 
    // component classes, for example:

    // engine.start();
    // transmission.shiftGear(...);
    // ...
    // engine.stop();
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Carlton

If you haven’t established good naming conventions for your functions, variables and classes yet, feel free to use the above-given tips. Having naming conventions makes your code read better and look more uniform and professional.

Connect with me on Twitter or Medium. You can also find the above information in my book 📕 Clean Code Principles And Patterns: A Software Practitioner’s Handbook 📕.

Top comments (0)