I write and think of code much differently now than i used to

Ifeora Okechukwu
24 min readSep 12, 2024

--

This article is inspired by another article with a similar title. I will be stating the things that have changed in the way i write code over the span of about a decade based on my mistakes and learning over same.

Poster

Here are 10 ways i now write and think of code differently:

I now believe readability is not subjective and is often confused with familiarity

Readability is often touted as being subjective. Well, no it’s not. Why do i say this ? Because readability is often confused with familiarity which refers to how aspects of software code is easily recognized by a person qualified to inspect it.

Familiarity depends on 2 things:

  1. Technical knowledge
  2. Domain knowledge

Technical knowledge is about having a good grasp of the grammar of the programming language used in coding as well as its’ quirks, other nuances and also the limits of the programming language when it interacts with resources of a computer e.g. memory. If you are not familiar with a programming language, you cannot easily make sense of what the software code is about.

Domain knowledge has to do with knowledge about what the software is about and why it was created. The codebase of a game software will surely contain bits of maths like vectors, trigonometry and geometry. The codebase of AI/machine learning software will contain bits of linear algebra and statistics. If you don’t know/understand or are familiar with vectors and geometry, you cannot easily make sense of what the software code is doing (Don’t stop please keep reading).

Without familiarity, we cannot begin to talk about readability. It becomes a weird discussion (as weird as i see it on Twitter or X). For instance, if you can speak and write english language, it means that you can read what is written by others in english language but not always be able to communicate what you have read to others (Stay with me keep reading).

Hence, you can only read what others write in english because you are familiar with english. If someone handed you a novel written in french and you are not familiar with french, you won’t be able to read it would you ? You see familiarity comes before readability (keep reading please)!

Yet, if your employer gave you a contract written in english with lots of legalese in it, you’d hand it over to your lawyer who is familiar with legalese because you are not familiar with it. The contract is written in english and you can read english so why hand it over to your lawyer ? You handed it to you lawyer because you are “technically” familiar with english language but you are not familiar with the domain of contract law. (again please keep reading)

Furthermore, let’s assume that your lawyer rewrote the contract (without adding to or taking away from the meaning and context of everything contained in this contract) using only words in english that you’d understand and made the legalese disappear into those words with simple sentences that you can relate to and are familiar with. Subsequently, your lawyer hands you this newly rewritten version of the contract. Would you now be able to read the contract with better understanding ? Yes, you would!

Readability is about clarity of the piece as well as the ease of reading that piece with understanding not just mindless reading. One cannot read with understanding without first being familiar with the language and the domain of the piece. Therefore, how readable a piece of code is can never be subject to opinion but to familiarity (domain and technical) with that piece of code.

In summary, i strongly believe that one isn’t qualified to say if some piece of code is very readable or not if you are firstly not familiar with the domain details and the technical details of said piece of code.

I now believe that less is not always more when it comes to writing code that is useful and valuable as a business product

I have gotten a lot of push back on this one and i get it. Our jobs as software engineers is extremely hectic and we are always looking for ways to minimize the amount of time and effort it will take us to get sh!t done.

However, when you are building a really key feature that has high chance it’ll need to be maintained frequently (therefore change) and is mission critical to the bottom line (i don’t only mean revenue to the business here — i also mean costs too) of software business, it pays more to be thorough with things like fault tolerance, security, error conditions and handling.

This invariably means that you would have to write more code than is necessary to get that feature or software usable by any end user.

Sometimes, to be able to test code better and write high quality tests for parts of the code that really matter, you’d have to break large code apart into loosely-connected pieces using meaningful indirection and well-crafted and absolutely necessary abstraction.

Finally, I’m using less fancy shorthands or non-idiomatic code. I don’t care about clever one-liners or paraphrasing variable names in a less known context. I’d prefer to write a verbose but meaningful variable name or if/else statement over cramming the same functionality in one line of code.

Less is not always more.

I use regular expressions only when absolutely necessary else i use a parser

Much earlier in my career as software engineer, i was very fond of regular expressions after learning about them and learning to use them well enough. Suffice to say that it didn’t take long for me to start over-using and abusing regular expressions.

Over the years, i have learnt to get clean off my addiction to using regular expressions for everything. When the regular expression looks unnecessarily complex, i use a parser instead.

There are times however, when i will opt to use both a parser and some small yet simple regular expression together.

I wrote more about this in this article here.

I don’t create abstractions until i am sure that i have separated code that will not/will hardly change over time from code that will frequently change overtime while ensuring i have a good understanding of the problem domain.

Have you ever noticed that if you have spent a considerable amount of time on a codebase (any codebase in fact), if you look through the change history (or version control history), you’d find source files you haven’t touched in a very long time (or even deleted) and others you have touched a lot ?

Even within a given source file, some groups of lines have changed drastically while others have remained unchanged and only pushed further down in the file.

I have taken much deeper notice of this over the years and made it account for how and when i refactor. The more stable parts of the codebase that i discover, the more i find that i can confidently abstract them for reuse or ease of change later (in which case i use a facade as a front).

I wrote more about this in this article here.

I now will more likely make use of a structurally typed language than a nominally typed language.

This i believe is more of a subjective opinion (I am not sure though). I have always had problems with nominally typed languages (like PHP, C#, Java). They feel unnecessarily bulky to me. They encourage a whole lot of boilerplate and are not very flexible which i why i hardly write them these days.

Also, i have found that i can get all the benefits of a nominally typed language from a structurally typed one without the inflexibility and boilerplate (except with generics i must add 😬).

I am not trying to say nominally typed languages are totally bad (please don’t get me wrong).

But overall, i feel it’s a much better coding experience using a structurally typed language.

This is just my opinion.

I mostly prefer to group logic very closely to the data that it acts on (Thanks ReactJS!)

If there’s one thing ReactJS got so right. I would say it fixed MVC for good. Prior to ReactJS, MVC was done quite wrongly (especially with AngularJS v1.x). A lot of MVC apps back then had logic in the controller that didn’t belong there. The logic actually belonged inside a domain model (POCO/POJO/function closure), a non-binder view model (DTO/function closure) or a data model. But this logic littered numerous controller actions and were often unnecessarily duplicated across multiple controllers.

What did ReactJS do to fix this ? It emphasised that logic that acted on a piece or related pieces of data be packaged and placed close to that data thus the component model was born.

Most of the business and data logic for your modern React app ought to be inside a hook not inside the component yet the hooks and UI state are all grouped inside the component. The JSX for your React app needs to only interact with a view model (or a view-binder model). It’s never an optimal situation to have data model logic or domain model logic within JSX. Keep data and the logic that it acts on close together and never separate.

This way of building software with GUIs has been massive success and have helped to deepen the true meaning of Separation of Concerns. I am often surprised when people have multiple definitions for the term Separation of Concerns. As far as i am concerned, Separation of Concerns has only one definition. Separating CSS and HTML into different folder locations isn’t Separation of Concerns (I don’t know who started that rumour). This is because CSS and HTML belong to the same concern (i.e. presentation or view or user interface) so they belong together. This is how i build most of the software i write now and haven’t regretted it.

However, you can’t always bring two bits overly together like with CSS-in-JS(X) (e.g. styled components).

Trying to create a non-standard DSL out of 3 different languages (belonging to separate and related layers) that are not of the same kind or type will always lead to problems. Many say this is in the service of locality-of-behaviour. Yet, keeping CSS and JS and HTML in different files yet within the same folder and organizing source files/folders by feature rather than by type would achieve better outcomes for locality-of-behaviour.

There are only a few cases overall where it’s okay to violate this.

I use classes (or similar) only when absolutely necessary else i reach for a function

I don’t think it is necessary to use a class in every situation. I think this is a remnant of the days of Java 1.5/1.6 SE/EE when a class was all-in-all. My guiding principle is if the code i am writing isn’t mutating shared state in an obvious and direct manner (i.e. directly related to and dependent on the logic), i will use a function instead.

I have often recently argued that the controller action for Rails, AdonisJS and Laravel shouldn’t be a method of a class. Why ? Because, there’s no mutated shared state across multiple controller actions defined on a single class. None!

Each controller action ought to be single function that had arguments injected into or passed to it since only one controller action can be called at one time per request/response cycle. There’s hardly anything shared in a direct manner between controller actions.

Functions are much more easy to create and compose better as well with much simpler organisation. I have found myself using functions more than classes these days and i like it. There are only specific cases where a classes make sense like database query builders and data/domain/view models.

I now believe that all of valuable software code revolves around 2 ideas: meaningful constraints and reasonable trade-offs!

I have come a long way from 2012 when i had my first real software coder job at Alpha-Beta Consulting in Lagos, Nigeria. Then, i was writing mostly JavaScript and legacy ASP. Back then, i saw constraints as irritations that should be moved out of the way.

Today, i see constraints as a blessing. It’s a very careless thing to do anything you like or write code anyhow you like without caring for potential consequences. This was something i learnt over the years.

If you build software like a small fun 2D/3D game or have a little profitable side project where in bugs and errors (even the serious ones) don’t impact customers and end users or the bottom line (profitability) that much, it’s very possible and okay to ship/deploy newer versions of the software along with such bugs and make that trade-off reasonably.

However, if you build mission-critical software systems for missile guidance or software for financial institutions, digital commerce or the stock market, it’s very possible that a seemingly benign bug or error can cause a huge outage or worse facilitate a heavy loss of money and time for customers and end users. So, before deploying newer versions, bugs need to be rooted out quickly and efficiently.

Making reasonable trade-offs makes all stakeholders happy.

I wrote more about this in this article.

I have gotten very good at writing tests. I typically advise writing more integration tests than unit tests and E2E tests because about 90% of software built today has more integrations than units.

For a long time. I thought that i knew how to write software tests. It turns out i only knew how to use and leverage testing frameworks to pass assertions that. weren’t even testing the code but testing mocks. My tests were. too brittle and broke whenever i changed the slighted thing in the codebase.

It took a while and i started to learn that the secret to writing tests faster and better is writing better code that is simple yet sophisticated (hard to mess up) and flexible (easy to modify).

I found extra benefit to writing more integration tests than unit tests. It ensure that my tests are more useful and reliable.

This is not to say that unit tests are useless. they are not!

I wrote more about this in this article here.

I now believe 9 times out of 10 that rewriting a software codebase is worse than incrementally improving it

Well, there are many pragmatic reasons why software would have to be rewritten. But, if it can be avoided, then all the better. It always seems like a good idea to rewrite because it would be a fresh start that sidesteps all the issues of the current system/codebase.

Rewrites will mostly be a good idea if time stood still!

But that is hardly ever the case is it ?

Finally, here’s are 5 bonus tips:

I now believe that in order to write good code on every software project, you must first of all write very basic bad code (moderately coupled and/or trashy) on that project in the early days. You screw things up really fast when you try to skip and go straight into writing good code (i.e. premature abstractions, speculative generality) and coding for scenarios that don’t yet exist.

It took a while to get this one and abide by it. Many years ago, i always wanted to try out the latest data structure and algorithm i learnt. I always wanted to code up the latest design pattern that excited me. This led me to write very obtuse code that served little to the overall point of the software/app i was building. It made everything so difficult.

The online tutorials didn’t help either because sometimes they helped me reinforce this bad practice of doing things a certain way just for the sake of it.

It was a lesson that i would not soon forget after i realized that depending solely on past lessons, past experiences and intuition is a bad way to become horrible at solving problems by writing code. At least it would have been better to opt for the good way to become horrible at solving problems by writing code.

You want to depend on what is being revealed to you right before your nose. Also, requirements can change too and quickly yet you have to take that into account.

I always say that every codebase speaks to you if you are observant and attentive. Yes, you read that right! 😁. The bad code (hopefully not heavily coupled together) you write is the only way to tell you what is wrong. Your past experiences might be useless if the software project you are currently on is very different from the ones you have worked on in the past. Your intuition is also only based on your past experiences and therefore not much help either.

So, except the new software project resembles what you have done in the past, it is required that you listen to the codebase and see the many ways it tells you how to change it.

I have always stated that D.R.Y (DON’T REPEAT YOURSELF) is never important at the start of a software project. Don’t be quick to apply a design pattern or abstraction (a good amount of the time, you don’t actually need any design pattern or abstraction) until you have looked at the bad code you have been writing so far and figured out how to make it better. Sometimes you may be wrong and you can backtrack and sometimes you may be right.

However, you can learn to do better each time you modify the codebase.

I now believe that Exceptions trumps Errors as values for everything regarding robust error handling in imperative-styled programming languages while Errors as values are better suited to functional-styled programming languages.

Exceptions rightfully has gotten a lot of bad rap as being non-performant because it does a control flow jump (on the machine code level — not L1 cache-able) and also gathers a stack trace whereas errors as values are more performant since no jump or stack trace is required.

Exceptions are also known to cause readability issues in the way they are written and also how excessively they are deployed across the codebase.

Finally, with exceptions, it’s difficult to track the flow of execution in the codebase. You have to track it yourself. Flow control with goto or longjump are said to make code unreliable because less information about the thrown error is known at caller sites higher up the call chain. This is simply not true as chained errors/exceptions (even as implemented recently in JavaScript) can carry information to callers higher up.

Also, i have never been a fan of unchecked errors/exceptions especially as they occur in dynamic languages like JavaScript:

function buildObject (data) {
// This JSON.parse() call can fail when data is not a string that is JSON-parsable
// Example of a string that is not JSON-parsable is a html string

JSON.parse(data)
}

These are all very valid concerns with exceptions as an error model.

When it comes to exceptions, there are a few tips i have learn’t along the way that make it easier to deal with them.

  1. Having lots of exceptions caught and handled at different levels of the call graph or call chain (or call stack) is the root of the problem. ALWAYS propagate exceptions towards the top of the call chain (or bottom of the call stack) and handle the exceptions very close the entry point of the software you are building. This way you will always have fewer try/catch block in your codebase and more robust error handling.
  2. If you must catch an error/exception several levels away from the entry point of you app/library codebase (there are times when this might be necessary) , never throw a different exception that excludes the error message and/or error code of the caught error/exception. The preceding caller code MUST ADD context to the new error/exception being thrown or re-thrown. See chained exceptions in Java.
  3. NEVER implicitly or explicitly handle AN ACTUAL error/exception several levels away from the entry point of your software codebase.
  4. Certain things that aren’t exceptions are treated as if they are. For example; searching for an item in a list and not finding it or reading data from a storage point and finding it empty. These are reasonably expected (or not unexpected) and hence should NOT BE regarded as errors or exceptions. An exception is any scenario that keeps the software from returning a range of valid yet satisfactory results for its’ purpose. An empty list is a valid result. A list that doesn’t contain the item searched for is a valid result. A network connection issue from client to server is not a valid result because it keeps us from returning a valid result. A very easy way to sort this out is to define and use runtime invariants. Failed invariants can later be converted to errors (Assertion Errors) if deemed a valid and satisfactory result cannot be provided.
  5. EVERY try block should never wrap more than one atomic or related set of operations or task(s). Stuffing try blocks with more that one atomic operation is how you can quickly have a bloated and unreadable catch block.
  6. NEVER assume and randomly add a try block around a piece of logic without first confirming that it can throw an exception/error of any kind.
  7. The reason why try/catch blocks are abused and deployed excessively in many codebases is because runtime invariants are not used well in said codebases. ALWAYS use runtime invariants!

These 7 tips above have made writing exceptions more tolerable for me in imperative-styled languages.

Another thing, exceptions work very well with the throws annotation that was added into Java. They help you tell when to expect a checked or unchecked exception. I am still very much surprised that most imperative-styled programming languages (like C#, Python and JavaScript) have not yet included this awesome annotation even though C#, Python and JavaScript uses thrown exceptions for error handling (although in Python you could use decorators to achieve similar).

I like exceptions more when using an imperative-styled language because i want to be aware when a given command or instruction (housed within a line of code) has gone caput! In other words i prefer coarse granularity of handling errors.

I would prefer errors as values in more functional-styled languages like Rust, Elixir and Haskell.

Now, about errors as values, it’s neither worse nor better than exceptions based on my own analysis like i alluded earlier.

In fact it’s more similar in certain ways to exceptions than you might imagine. One could regard the conditional checks for errors in a result as being similar to a catch block. Therefore, if the error is returned, then it can be regarded as being re-thrown (as is the case with exceptions) else it is handled in place.

package main

import (
"errors"
"fmt"
)

func divide(dividend int, divisor int) (int, error) {
if divisor == 0 {
return 0, errors.New("Cannot divide by zero")
} else {
return dividend / divisor, nil
}
}


func main () {
number1 := 15
number2 := 0

// The Go language is imperative-styled and so having errors as values
// is not really what i expect and i honestly do not like it used here
// as it doesn't feel natural in a command-based imperative environment.

// Yet, it's not the worst thing in the world...
result, err := divide(number1, number2)

// acts like a catch block
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("Result: %d", result)
}
}

I find that most of the valid concerns of exceptions also apply to errors as values. Errors as values can be extremely verbose forming intense boilerplate.

Also, with errors as values, error handling is never consolidated but spread out across the entire codebase in a non-coherent manner. This also makes the code ugly

errors as values can also mean that one cannot easily reuse error handling logic across multiple call sites. errors as values are good yet over-cautious from the standpoint of an imperatively-styled language but nevertheless still being over-cautious leads to building more reliable systems.

This is why i like the way Haskell deals with the issue from a functional language perspective: errors returned are handling the way exceptions are handled — a combination of pattern matching and monads!

On the other hand, exceptions cause you problems when you work with callbacks. You suddenly cannot rely on breaking the flow of execution from the point of failure. What good will that do in a callback-based programming model with or without an event loop ? You now need to very much depend on the fact that the callback MUST be called no matter what. even after failure.

In this callback-based model, errors as values fit the bill. Remember the early days of Nodejs ?

Exceptions do not thrive in functional situations! Also, errors as values work much better in functional situations.

Yet when something goes terribly wrong — like an OOM, error as values are not able to add context to these unrecoverable errors. The system just crashed with no extra information about how and what was responsible or which area in my codebase i should begin looking for a reason. Was it a dangling pointer being accessed to modify memory ? I don’t even know as there’s no stack trace.

Exceptions are able to get me the necessary information even in the midst of an OOM error.

People say that returning errors from functions instead of throwing them helps ensure more predictable code. This is true yet thrown exceptions are a not completely bad error model but a poorly-designed one. The reason why people insist on this is because a function/method call can fail unexpectedly since it isn’t obvious that a failure can very well occur. This is not because something is inherently wrong with exceptions but the absence of a well-designed throws annotation in static languages that have exceptions baked into them.

The problem with the current design is that we wish/assume that every function call most likely will not find itself in a failure scenario. But, reality is very different. There should have been a limit. Also, identifying errors/exceptions via distinct class/type names instead of grouping them as a family is a monumental tragedy in the history of computer programming.

Why Java and C# decided to have a FileNotFoundException alongside an IOException as a parent class is one very spectacular design mistake i can’t shake off easily. Every I/O process uses a file descriptor whether reading a from a file or network socket. It would have been easier to have a final code field on the IOException class that returns a code (e.g. FILE_NOT_FOUND) telling you whether the IOException refers to a file error or a network socket error which both relate to I/O.

So, it is my personal opinion that exceptions are rightfully given a bad rap mostly due to horrible design that they are forced to have.

I now believe that using invariants is the best way to write less buggy code that is also self-documenting and helps to provide context much more than explainer comments can ever do. As a bonus, it also helps speed up debugging!

I believe the reason for this horrible design is because runtime invariants are not baked right into a programming model built into the language. For dynamic languages like JavaScript, invariants are even more necessary at the language level. Yet, they are still necessary for strict languages that even already have static types (static types are compile-time invariants).

There are certain types of exceptions that do not specify what the problem really is.

Types like NullPointerException or ArrayIndexOutOfBoundsException don’t point to the actual issue. They just announce that some low-level instruction down the line failed. The reason the instruction failed remains unknown or more correctly opaque. Now, this reason is opaque to the programmer or engineer exposed to the exception/error message because it is trapped inside the buggy logic the programmer has written.

This is where runtime invariants come in. Invariants are not a novel concept but they are not very widespread in our industry. Most software engineers hardly use them yet they are invaluable to writing bug free code.

When you execute JSON.parse(..) in a JavaScript application and it throws a SyntaxError , the error doesn’t point to the actual issue. The error simply announces that the string passed to JSON.parse(...) is not a JSON string. The SyntaxError is a symptom of the actual issue and the actual issue occurred long before the SyntaxError was thrown. An invariant makes the opaque reason transparent.

I believe having a language that is invariants-based will help programmers avoid these instances. If invariants are baked right into programming languages, then exceptions like NullPointerException or ArrayIndexOutOfBoundsException shouldn’t exist.

In the meantime, user-land invariants will suffice for now.

Here’s a partially spec-compliant HTML Parser which i wrote in python just for fun showing how invaluable invariants are (P.S. the invariants are the assert statements)

I now believe that all these acronyms & laws: YAGNI, KISS, DRY, SOLID, Law of Demeter, Hyrums’ Law, TDD, DDD, Law of Least Surprise, Locality of Behavior.

They all speak to (or try to answer) the same question/issue >> How to manage Cohesion & Coupling well in any codebase ?

It’s all the same issue but analysed from different perspectives.

Similar to how a group of blind men feeling for the same elephant from different positions/perspectives and all coming to seemingly unrelated conclusions. However, the conclusions are more related than unrelated.

This is the same for all these acronyms and laws in software engineering.

When you finally understand this like i did, they all won’t look like distinct topics to you anymore. it will all make sense to you.

For instance, D.R.Y. stands for “DON’T REPEAT YOURSELF

  • If it’s boilerplate code or setup code for some task that’s exactly the same across multiple cases or contexts. Don’t repeat it!
  • If it’s code for a non business-related task that is straightforward and is hardly ever going to change in the future (e.g. http requests, unit conversions, writing to a file, converting from one data form to another). Don’t repeat it!
  • If it’s code that pertains to a business case or task that serves a given purpose in the software system under specific situation used within the same context/domain boundary. Don’t repeat it!

I now believe that it is better to be initially skeptical about what you learn as the best way to write software code from others especially from online tutorials. Therefore, question everything with reason!

When i learnt Java between 2006 to 2010, i thought that this was the best way to setup the representation of computer programs like one that modelled 2D shapes as well as the calculations for the area and perimeter of said 2D shape.

Using the Java language, i believed then that everthing had to be modelled as a class. I was wrong!

See below: 👇🏾👇🏾👇🏾👇🏾

interface Shape {
public int area ();
public int perimeter ();
}

// There's a lot of uncessary ceremony here that doesn't add to the substance
// of the codes' purpose and the reason why it was created.
public class Rectangle implements Shape {
private int width;
private int height;

public Rectangle (int horizontalSide, int verticalSide) {
this.width = horizontalSide;
this.height = verticalSide;
}

public int area () {
return this.width * this.height;
}

public int perimeter () {
return 2 * (this.width + this.height);
}
}

As you can see, this code above does not reallyLet me show you:

First and foremost, a Rectangle (in Java) should NEVER be a class with methods for 3 reasons.

  1. As a class with methods, it doesn’t hold any real world realization of a concept that can interact with other objects and can be interacted with by others based on a contract. Therefore, it should be a simple struct with no methods.
  2. The defined method of perimeter() is for quadrilaterals in general not just a Rectangle. So, if i were to define a Square or Rhombus class, would it be okay to extend the Rectangle class just to share the perimeter() method implementation ?
  3. As a class with methods, it doesn’t have mutable shared state. When the values for width and height are set during the construction of the object, they are meaningfully only ever set once for the entire lifetime of the object. Therefore, there’s no mutable shared state amongst the method. The aim of setting it up as a class with methods falls flat on its’ face.
  4. As a class with methods, the methods area() and perimeter() are self-contained operation-based routines (not cooperative action-based routines — i.e they don’t require any other state defined elsewhere inside the class constructor). Therefore, they ought to be functions and not methods of a class.

The calculations for area() and perimeter() should not to be bound to a specific value for width or height but are to receive these values (i.e. width and height) as arguments.

Hence, a Rectangle (in Java) ought to be defined like so:

// Since the class has no methods we can take it as a "Java struct"
public class Rectangle {
public int width;
public int height;

public Rectangle (int horizontalSide, int verticalSide) {
this.width = horizontalSide;
this.height = verticalSide;
}

// No methods
}

Same with a Circle or any other 2-dimensional shape for that matter.

// Since the class has no methods we can take it as a "Java struct"
public class Circle {
public int radius;
public int diameter;

public Circle (int radius) {
this.radius = radius;
this.diameter = 2 * radius;
}

// No methods
}

Where do we put the logic for the calculations of area() and perimeter() ?

Well, we set it up as stand alone methods on a final class (not bound to any struct) like so:

public class Main {
// Entry point
public static void main(String[] args) {
System.out.println(
"area of circle = " + Shapes.Operations.areaOf(new Circle(13))
);
}
}

public class ShapeOperation {
public static int areaOf (Object object) {

if (object instanceof Rectangle) {
int w = ((Rectangle)object).width;
int h = ((Rectangle)object).height;

return w * h;
}

if (object instanceof Circle) {
int r = ((Circle)object).radius;
double r0 = ((double) r);
double a0 = Math.PI * Math.pow(r0, 2);
int a = ((int) a0);

return a;
}

return 0;
}

public static int perimeterOf (Object object) {
if (object instanceof Rectangle) {
int w = ((Rectangle)object).width;
int h = ((Rectangle)object).height;

return 2 * (w + h);
}

if (object instanceof Circle) {
int r = ((Circle)object).radius;
double r0 = ((double) r);
double p0 = 2.0 * Math.PI * r0;
int p = ((int) p0);

return p;
}

return 0;
}
}

public final class Shapes {
public static final ShapeOperation Operations = new ShapeOperation();
}

The code above is a lot more generic and realistic now.

You can also compose this with other code like so (assuming you created a 2D canvas rendered on a screen):

Canvas canvas = Canvas.from(Canvas.Texture.GRID);

canvas.setWidth(1000, Unit.PIXELS);
canvas.setHeight(600, Unit.PIXELS);
canvas.render();

// Using `Rectangle` class without methods from above
canvas.setEntryPositionTo(
Canvas.toPixelPoint(300, 100)
).drawObject(
new Rectangle(60, 20)
);

// Using `Circle` class without methods from above
canvas.setEntryPositionTo(
canvas.getCenterPositionIn(Unit.PIXELS).scaleBy(2)
).drawObject(
new Circle(13)
);

// This is much better and readdable code
canvas.setEntryPositionTo(
Canvas.toPixelPoint(700, 100)
).writeText(
"area of rectangle = " + Shapes.Operations.areaOf(new Rectangle(60, 20))
)

I hope you got a thing or two from this. Let me know in the comments 👍🏾

--

--

Ifeora Okechukwu
Ifeora Okechukwu

Written by Ifeora Okechukwu

I like puzzles, mel-phleg, software engineer. Very involved in building useful web applications of now and the future.

Responses (1)