Programming went wrong, OOPS!

Prasanna R Balasubramani
Tech Tablet
Published in
5 min readMay 5, 2019

While a well thought out OOP design could greatly benefit an organization, there are a few basic pitfalls that programmers could easily fall into.

Abusive Inheritance

It’s always important to strictly adhere to the most important rule of Inheritance and ask yourself, “does this satisfy the IS-A relationship?” A “no” for an answer is a bad omen in OOP for Inheritance.

Example: Alice is given a task to model products for an Automotive company. As an MVP, she must design a Car and a Truck.

Alice initially inherits both Car and Truck from a Vehicle since a Car IS-A Vehicle and a Truck IS-A Vehicle too. Alice retrospects her initial design and now wants to reuse as much code as possible and chooses to have a multi-level inheritance where Truck inherits from a Car and the Car inherits from a Vehicle. She immediately feels happy about code reuse and passes it on to her reviewers.

When Bob reviews her design, he asks her, “A Truck IS-A vehicle… alright.. but why IS it A Car? Why not have a different class like FourWheeler inherit from Vehicle and let both Car and Truck inherit from the FourWheeler?”. That’s a good question! She could have had the same magnitude of reuse with a FourWheeler with better Inheritance.

Abusive inheritance would not only seriously affect the readability of your code but also make your maintenance a nightmare. You could easily fall into a limbo of deeply inherent behavior traversing up and down the ladder.

Abusive Encapsulation

Declaring private state variables and exposing them over getters and setters is probably the most common encapsulation every OOP programmer would have written. This allows us to massage our state values while setting and getting them. By massaging in getters and setters, the original state is already lost and what we have now is only a computed state.

For example, imagine massaging a DateTime and converting it to UTC in the setter. The original timezone from where the request has originated will be lost forever.

Mutability & Large Scopes

Editing the same object without creating a copy of the same complicates the code. Consider the following example,

public void addBrakes(Vehicle vehicle){}
public void addLeatherSeats(Vehicle vehicle){}

The return type of these functions is void. Hence, the only way these functions could communicate their processed state outside of themselves is by editing the vehicle object directly or by modifying largely scoped variables. This will once again make unit testing and maintenance very difficult.

POJOs over JSON Objects

We should be mindful that JSON Objects are not quick win solutions for deserialization because of the flexibility that they offer. They help us when the response that we are trying to deserialize is highly dynamic and would not fit into a fixed POJO. I am often surprised when I come across forums that downplay POJOs and suggest the usage of JSON Objects as a replacement. POJOs are well defined and offer the following

  1. Readability,
  2. Type Checks,
  3. Support for inline annotations for validations and frameworks like Hibernate.

Example: Alice tries to practically demonstrate the importance of POJOs to Bob and asks him to come up with a method to “getMobileNumber()” given a JsonNode of a Person.

public String getMobile(JsonNode person){
//To be filled by Bob.
}

Bob writes the above code and immediately asks Alice,

  1. Alright, how does a person object look?
  2. Where will I find the Mobile number in Person?
  3. What should be the data type of Mobile number?

Alice shares the following and looks at Bob with a victorious smile!

public class Person{
String firstName;
String lastName;
int age;
Phone phone;
Address address;
}
public class Phone{
String mobile;
String home;
}
public class Address{
String street;
String city;
int state;
Phone country;
}

This is a very simple illustration of the problem. Now imagine how complex navigating through a JsonNode would quickly get when the structure has a series of Composition and Inheritance within it. Also, for a developer, there is no way to type check at compile time. With JsonObjects Mocking the data for Unit Testing would now be a nightmare!

Focused Functions

A function that performs more than one behavior is a big NO-NO! This would seriously dent reusability and readability. Unit testing such a function would be more difficult and time-consuming.

In the following simple example, Bob wants to read a file that has Person records in it and extract the email addresses from each record. Bob decides to write the following function

public List<String> extractEmailIds(File personFile){
//Read personFile into a list of person objects
//Extract email ids of each person and return it as a collection
}

A few months later, Alice is working on the code base and she wishes to extract all the phone numbers from each person. Luckily, Alice notices Bob’s lazy programming and decides to refactor the code into the following

public List<Person> extractPersons(File personFile){
//Read all persons from the file and return the collection
}
public List<String> extractEmailIds(List<Person> persons){
return persons.stream.map(Person::getEmailId).collect(toList())
}
public List<String> extractEmailIds(List<Person> persons){
return persons.stream
.map(Person::getPhoneNumber).collect(toList())
}

This problem might appear minuscule based on the given example. But imagine a scenario where you make multiple API calls, perform complex computation over their results and aggregate them into a resultant object structure, all in one function. How complex would refactoring or unit testing such a function be? See the pain? That’s tossing reusability from the top of a cliff, just like that!

Self-documenting code and block comments

Most organizations have adopted an agile methodology for maximum productivity and started coming up with self-documented codes. Self-documented code, in short, is breaking down a function into smaller functions that focus on one thing and one thing only. The names of these functions would communicate what the function does making it very easy to follow. While the self-documenting code is totally possible with smaller functions, do not be hesitant to add a block of comment in your code whenever you think that your comment would make maintenance easier. Adding an example of the input/output when the structure is not very clear is one of my personal favorites. Consider the following prototype,

/**
* Returns a HashMap of the format <emailId, List<mobile>>
* Ex: <bob@gmail.com, List["123-456-7890", "111-222-3333"]>
*/
public Map<String, List<String>> groupMobileByEmail(List<Person> persons)

Though the function name is indicative of the response structure, adding a block comment indicating the response structure makes this method a no-brainer to understand.

I usually block comment the following on a function,

  1. What does the function do?
  2. How (algorithm) does the function do the “what”?
  3. Why is the “what” necessary (or reason for needing the “what”)
  4. What does the function take (input)?
  5. What does the function return (output)?

A well self-documented code could answer a few of the above but not all. Whenever there is a gap in communication, please do not hesitate to fill it up with comments.

Conclusion

While there is no one right way to program or one strict rule to follow, these rules usually make the lives of you and your fellow developer colleagues better!

--

--

Prasanna R Balasubramani
Tech Tablet

Discuss, Reason, Learn & Share! Full Stack Dev @ExpediaGroup. All opinions are my own.