14th: Reading other people’s code will always be a bad user experience

First, my bold statement is this: reading other people’s code will always be a bad user experience.

The reason is simple: code readability is mostly subjective.

In year 2000, I had to set up Mailman for the company I was working and maintained the application for a few months. I remember when I first read its source code, I was so amazed how beautiful and elegant the code were. Today, I just tried reading some of them again, I was like, “that is not easy to read at all”.

This week’s idea is: I am making a list of standards when reading other people’s code.

(1) The reason of reading

I was exhausted when I tried to read the source code of Mailman again.  More importantly, I no longer have any need to understand it and I also do not remember how to write Python.

The motivation of reading the source code is probably the first important element. If I don’t have to run it, test it, and even maintain it, it’s unlikely I will ever like it.

The take-way message is:

Do not judge other’s code when you don’t have enough context unless you are going to take responsibility with it. 

(2) The stage of the project

I will give an example of this point. The example is written in Scala. Let’s say I have an entity like this:

case class Entity(field1: String, field2: Int)

I would like to validate the fields, so I can start with this simple class:

class Validation1 extends Logger {
  def validate(e: Entity): Option[Entity] = {
    if (e.field1 == "hello") {
      logger.info("I say hello")
      None
    }
    else if (e.field2 == 10) {
      logger.info("10 is a bad number")
      None
    }
    else
      Some(e)
  }
}

I hope it’s easy enough to understand. The problem of this version is: I have to do if-else statements and when I have more validation logic, this function will become very ugly.

Therefore, I can refactor it to the following version:


class Validation2 extends Logger {
  def validate(e: Entity): Option[Entity] = {
    for {
      _ <- noHelloRule(e)
      result <- noTenRule(e)
    } yield result
  }

  private[this] def noHelloRule(e: Entity): Option[Entity] = {
    if (e.field1 == "hello") {
      logger.info("I don't say hello")
      None
    }
    else
      Some(e)
  }

  private[this] def noTenRule(e: Entity): Option[Entity] = {
    if (e.field2 == 10) {
      logger.info("10 is a bad number")
      None
    }
    else
      Some(e)
  }
}

What I am doing now is: each rule becomes a function so I can use a for-comprehension to connect them together.

However, the problem of this version is: the class has 2 private functions and all of them have to be invoked inside the main validate function. If I need to add more validation logic, I will add one private method, and then update my main function. It’s annoying.

Hence, I can refactor one more time.

trait Rule[A, Result] {
  def validate(e: A): Result
}

trait EntityRule extends Rule[Entity, Boolean]

case class NoHelloEntityRule() extends EntityRule with Logger {
  override def validate(e: Entity): Boolean = {
    if (e.field1 == "hello") {
      logger.info("I don't say hello")
      false
    }
    else
      true
  }
}

case class NoTenEntityRule() extends EntityRule with Logger {
  override def validate(e: Entity): Boolean = {
    if (e.field2 == 10) {
      logger.info("10 is a bad number")
      false
    }
    else
      true
  }
}

class Validation3 {

  private[this] val rules: List[EntityRule] = List(NoHelloEntityRule(), NoTenEntityRule())

  def validate(e: Entity): Option[Entity] = {
    if (rules.forall(_.validate(e)))
      Some(e)
    else
      None
  }
}

In this version, each validation logic becomes a single class with a single purpose because it represents only one rule.  The Validation3 class has a local variable holds all the rules. I am hard-coding the instance of the concrete rules, but later I can use different approach to inject rules dynamically. For example, I can use config file or attribute decorating a rule class, and then use reflection or meta to discover them.

Now, the question to ask is: why the heck I would do the third option at the beginning of the project, or with only a few validation rule and each of them is just a simple if-else?

Wait, some smart people might interrupt me. The third option is not Scala, or not pure functional at all. They are probably right. However, the take-away message is this:

It’s better to avoid over-engineering at the beginning. Instead, focusing on how to get the requirements right and readable, as well as validating the implementation with stakeholders, as opposed to overly generalize or optimize the implementation in the early stage. 

(3) the context of the product and the company 

As I mentioned earlier, code readability is highly subjective. However, my personal belief is: all programming languages are the same fundamentally. Proper choice of algorithm and data structure, separation of concerns, and SOLID (or following some similar principles)  are more important than particular language syntax.

This should be particularly true when building commercial software. Writing pure scientific applications could be a different story. The reason is: after the product is launched, 6 months later, people might not remember what the code is about. Because of all kinds of staff turnover, for example, original dev team has been switched to a different department, or external team has finished contract, some new people will end up maintaining the code-base they have no idea of. Those poor guys could be as dumb as me, or they could be any level, but they need to be fast enough to debug and enhance the application.

Here is a bottom-line, especially for people do not believe it: business software needs to be maintainable and enhance-able by any software developer, as long as they know how to compile, code, and test. Therefore, the take-away message here is:

Depending on the type of the project and the company you are working for, simplicity is always the key. We are never writing code for ourselves. We are writing code for, one day, another person has to add a new feature to it or debug why it fails. Always KISS (Keep it simple and stupid), but flexible (so other people can easily change).


In conclusion, I believe we should refer to at least above 3 points before judging people’s code. However, as a software engineer, I know no matter what I say, people will always have this expression when they read other people’s code.

 

 

4th: My idea of presenting functional programming

Today I want to talk about programming, particularly, programming paradigm.

A quick background

I spent a year learning and writing Scala, which is a fascinating language. But I could not get my head around why we wanted to write functional way. And Martin Odersky even said this in one of his talk:

“if you have to inject dependency to your code, something is wrong”.

Probably not exactly the same words, but close enough.

After that year, I switched back to writing C#. Then I suddenly got a hit about why writing functional is such a joy. The code can be much more concise, so easy to write test, and naturally supports concurrency without using lock because no state is shared. Everything starts making more sense.

Give a talk about it

My current director asked me to give a talk about using functional in the project I have been working on. I told him yes, but I am not sure when I will have the time to do so. I actually like giving talk to the team. It feels like sharing your thoughts and learn from each other.

I did it on a few different topic after I joined Starbucks. I gave a presentation about my research long time ago in front of couple hundred people, I enjoyed it. Unfortunately, I always find it challenging to make the commitment  to become a regular speaker in conferences. I admire those people who can.

But how do I really talk about functional programming to the people who have never done this before, when myself is not even a guru of functional programming?

The other day when I was running, a few slides came up in my head. So my idea is using them, hopefully.

My potential slides

It is just a tool

First, forget about the word “functional programming”

forget about the word functional programming
forget about the word functional programming

It is possible that some people will be offended. So I have to make the claim that this is only my personal opinion.

Second, it is important to remember, “functional programming” is just another programming paradigm. It is a style, and it is a tool to write code.

We use the right tool at the right time for the right job.

It is the same idea that I will probably not use a hammer to mow lawn, even though it might work. I will use a knife to chop vegetable or meat, not a scissor, even a scissor will definitely do the job as well.

Choose the tool wisely.

So choose functional paradigm when the situation is right.

I particularly like  Anjana Vakil’s talk about programming across paradigm, if you never watch it, I strongly recommend it:

Different perspective

The following is how I believe most people consider a good software product:

If you Google “characteristics of good software”, pretty much the same thing.

  • Workable: the software is usable, in a working state, and it can realize business values;
  • Scalable: the software can be scaled in terms of performance, the number of users, as well as the number of business cases.
  • Testable: the software can be tested from the unit level.
  • Changeable: I like to say the word “changeable”, which means the team (not just the individual who originally writes it at the first place) can comfortably make change to the code. Others may prefer “extensible”.
  • Readable: The code is readable and understandable, or the software itself is well documented on the business case.

Note: extensibale and scalable are 2 different things here. If you notice, scalable is referred to the software can scale to more business cases, while extensible is referred to any developer can make change to the source code.

There is a reason a triangle being there.

The bottom is always “workable”. A software must work. If a software does not function, constantly crash, or does not deliver business values, nothing else matters.

Therefore, from a company’s perspective:

It is paying for a workable software that can deliver business values and meet business requirements. The biggest expectation from a company’s perspective is “it works”.

Next expectation from company’s perspective is, “can the software scale to meet more requirements”.

Generally, a company cares less everything else in the source code, unless that is a software company (maybe, hard to say, we all have heard how poor Oracle is written anyway). Therefore, no one will care how beautiful the code is written, or if it is using functional, procedure or imperative.

Only developer cares.

Actually, I probably should say only developers with self esteems care.

In contrast to the previous slide, the following is from software developers’ perspective:

Developers still care about the final product work. But we all know how easily a developer can be distracted in his/her own interest:

In the reversed order, developers care more about the things like how beautiful the code is written. Interestingly, the more ego the developer has, he or she is likely more focusing the things on top of the triangle.

Having developer ego is normal, in fact, it may be a good thing sometimes. It may not be sometimes though.

The funny thing about those things are important to developers, are naturally supported by functional paradigm:

  • Readable: small and single purpose functions, easier to reason the code logic, thanks to deterministic fashion.
  • Changeable: this may not be true for someone is new to functional programming; however, since FP advocates function composition, it is actually true that it is easy to make a change to composite existing function to make a change. Additionally, because it is designed to have no state shared and ideally each function is pure, it is less likely to impact existing business logic when making a change.
  • Testable: I did not believe this before, but it is definitely true that writing unit test for functional code base is way much easier and faster than writing unit tests for imperative style. No mocking framework is needed, and property-based testing is supported.
  • Scalable: One big advantage of functional paradigm is its strength in concurrency handling. Because it is so easy to make change, the source code can also be scaled to meet different business requirements.
  • Workable: nah, who cares if it works, it is beautifully written.

Not: functional code is generally/arguably considered hard to understand. That is actually caused by developer ego. As I mentioned earlier, it may or may not be a good thing. If the ego is too high, abusing functional style can be intimating for others to read. But it does not have to be that way.

What does it mean?

So we see company and developer have different perspective when looking at a software and its source code, but what does it exactly mean?

Of course, writing a workable software is the most important thing, because that’s what developers are paid to do. Also, because developers have ego, and different people like to use different paradigm, how do developers work together building a workable software product?

The answer is: modularization.

Other people may see it as Microservice architecture.

What?

Yes!

No matter we are building a client application, or a backend application, we will write it into small modules. Each module represents a business feature, and then we stitch them together.

Each module is communicated through contracts, i.e. events driven. Now each module can be coded in different paradigm.

Some module may be good to use functional paradigm because it has a lot of asynchronous processing, another module might be better to use imperative. It does NOT matter. A paradigm is just a tool. Use it in the right situation. I recommend to adopt functional paradigm in pure business logic.

Conclusion

This is an angle I haven’t seen very often:

  • writing modularized application so developers can avoid paradigm difference….