Another Browser Rendering Nitty Gritty

I did a post before on how browser rendering works previously though, but I feel it was not enough.

There are certainly gaps in my head I wanted to fill, and I came across this conference talk which I find really informative. Not the best presentation but it's decent.

Check your self right here:

There are several key points I'd like to make about this video if you prefer not to watch it.

  • When HTML parser reaches <script> tag, it will halt parsing, fetch the script and execute it before continuing to parse.

  • When a <script> tag is found and parsing is halted, the browser will create a new thread in a new process with the browser to search for external images and CSS to fetch in parallel. It probably will also look for another <script> tag to download, but I'm not sure about executing it.

  • On initial render, it needs all the Parse Tree to finished before it proceeds to making a DOM, and it needs all DOM nodes and CSSOM to be completed before being combined into a Render Tree, and so on.

  • JavaScript can interfere with the DOM and CSSOM on initial render.

  • For subsequent rendering, it will set at a regular interval when it will reflow and repaint. Every time we mutate the DOM, it will still be immediately alter the Render Tree, but then it will not immediately proceed to the next stage (layout). Altered nodes in Render Tree will be marked dirty, the batch will traverse the tree and find all dirty trees at a regular interval, so multiple dirty nodes can be reflow and repaint on a single flow.

  • Immediate reflow occur on several actions, such as doing a font size change, resizing the browser, and accessing several properties like node.offsetHeight!

Some Performance Insights
  • Do all reading in one go, writing in one go.

Essential Unit Testing and TDD

I have used unit testing within TDD (Test Driven Development) for quite sometime, yet I did not really understand why we are doing it.

Everybody tells me it's the best practice, so I did it without hesitation.

I thought I was doing TDD although I wrote my test last, after the code was written.

After making my own research towards unit testing and test driven development, I felt enlighten.

Unit Testing

Unit tests usually test on the scope of a class, module, or a component. Basically it test the smallest applicable unit on your software codebase.

Unit testing is not about about finding bugs, it is not merely a test at all!

The goal of making a unit tests is to help you design your software component.

You have to know in mind what your code are supposed to do before even start jabbing around on coding. Without it, you can't even start unit testing. It is often where we go implement a bunch of code without the right design in mind, we're just trying to figure it out along the way, therefore waste a great deal of time.

When you find your code difficult to test, it's a sign that you got yourself a smelly code:

  • Too much dependencies therefore you need to mock each one of those which is painful. It tells you that your code is too tightly coupled.

  • Unit test is way too long because your unit does multiple of things (low cohesive).

  • Common functionality is found among multiple different units violating DRY (low cohesive).

Unit testing also enables one to document cases in which a unit has been tested with. It also forces you think and probably find edge cases where a bug was found.

It can also be a regression test because simply changing your codebase has a potential to break your test, therefore having you to refactor the test and probably caught bug along the way.

Unit Testing Within TDD

Unit testing matches well with test driven development where forces you to write the simplest code that is necessary for the feature to work (test pass).

By doing this, you are avoiding unnecessary code, otherwise known as YAGNI (you ain't gonna need it).

A typical workflow would be a "red, green, refactor".

You write a unit test for the feature you are about to implement. Run the test and get a failed test (red).

Then you make the simplest code to make the test pass (green).

Afterward, refactor what's necessary.

Make it work, then make it right.

Selective Unit Testing and Techniques

Not all unit are meant to be unit tested. Some yields great benefit while some are just not worth the cost.

The cost of making unit testing are time needed to make the unit test and to maintain it.

If the ratio between cost and benefit is too high, it is better to avoid it.

Steven Anderson said that the benefit of unit testing is correlated with the non-obviousness of the code under the test.

In other words: abstraction level of the code.

If the code likely to have high abstraction level, a further design assistance generally needed.

If you can't figure out what the code was doing on a single glance, a further verification through unit testing is beneficial because it would be daunting to check all possible cases manually.

He also argue that the cost is correlated with the number of dependencies your code has, where more dependencies means more time needed to mock and adapt to future changes of your dependencies.

To sum it all up:

  • Complex code with few dependencies: cheap and highly beneficial to unit test.

  • Complex code with many dependencies: you will need need to refactor the code to two parts: a part which handles the complex logic and another which glues (interacts) with many dependencies. Unit test only the one that contains the complex code.

  • Trivial code with many dependencies: not worth to unit test.

  • Trivial code with few dependencies: not worth to unit test.

As for the complex code with many dependencies, the technique is to pull out the dependencies out and form an entirely new unit.

Use that new unit to interact with all of the dependencies needed and pass only what is needed within the dependencies to the unit that requires it.

What it means that when a unit expresses that it uses dateClass.getTime(), it does not actually need the dateClass, it needs only the time. So just pass getTime() from the unit that glues all the dependencies to the unit that handles the complex logic.

Clean Function

I have learnt for a couple of days prior about how to write a clean function from Robert C. Martin or otherwise known as Uncle Bob.

The whole idea in my opinion always embarks on how expressive it needs to be. If your code is readable and easy to understand, then you have written a clean code.

Sometimes we as a programmer wants to show our co-workers and the readers of our code how smart we are. We tend to provide fancy ways of doing stuff. Making everything as one-liner as it can. If they can't comprehend what we have written, well, they are just plain dumb!

Uncle Bob said that this is a work of an amateur. Professional work the other way around.

Professional want their code to be expressive, understandable, and readable. It needs to be as concise and simple as much as possible!

Why? Because we work as a team, we want to ship fast avoid bugs! If you are the only person responsible for that part of code you written (because nobody understand it except you), then what happen if you leave?

Got the idea?

Let's dive in.

Naming

Naming function is critical to your program.

It should be a verb and it needs to clearly explain what is the intention of the function you are writing, so fellow programmers does not need to dive inside your function to see what it does, a glance from the name itself should be enough!

Do not present hidden implementation (side effects) that goes beyond the function name. Don't say that your function does A, but turns out it also does B.

Small

The first rule he aligns is that function has to be small! Functions needs to be two, three, or four lines long.

Do one thing

Function has to have one responsibility only! It needs to focus on one specific thing and abstract other implementation into a separate function.

Extract big function into smaller chunks, but do not over extract function if a restatement is the only effect it yields.

Sometimes it's hard to do it all from the beginning. Rule of thumb is, write all your implementation function first, then when you are finished, refactor it.

First make it work, then make it right!

Big function that polished into small abstracted steps seems to do more than one thing. It might look as follows:

function foo() {  
  doA();
  doB();
  doC();
}

This little abstractions made the function look like as if it does a lot.

So does it does one thing or three things?

One level of abstraction per function

Well, Uncle Bob state that if the statements within our functions are all at the same level of abstraction, they are definitely do "one thing".

What does it mean abstraction, anyway?

Well, each function you extract from the big function is an abstraction, because you are hiding it's implementation detail by wrapping it to a new function.

Making use of the function does not require you to know how it does it, only what it does (from the name).

So, what is level of abstraction?

appendToArray() and parseHTML() is two different level of abstraction. appendToArray() has simpler implementation therefore lower abstraction level. You certainly know how it does the way it does. Meanwhile parseHTML() is much more complex, more magic is going on, you are not entirely sure how it parse HTML.

Making sure your function has the same level of abstraction is key. One way to look level of abstraction is through LOC (lines of code) it has.

Another way is that a function and all it's extracted method should read well like a paragraph.

Here's the snippet from Clean Code book by Uncle Bob.

To include the setups and teardown, we include setups, then we include the test page content, and then we include the teardown.

To include the setups, we include the suite setup if this is a suite, then we include the regular setup.

To include the suite setup, we search the parent hierarchy for the “SuiteSetUp” page and add an include statement with the path of that page.

To search the parent. . .

Arguments

The best number of argument for a function is zero. More arguments means more confusion. You need to remember what to pass, the order of what to pass, and more testing.

Less is better.

Except, if the system you are trying to create has a natural order and requirements. For instance is a coordinate number setCoordinate(x, y).

Exceptions

Exceptions are beneficial since it's able to simplify error handling.

But handling error itself is a specific one thing of what a function does.

Uncle Bob wants you to extract your try/catch block.

function foo() {  
  try {
    doBar();
  } catch (err) {
    logError(err);
  }
}

Single Responsibility Principle

This is a brand new concept I just learnt. Having this concept in mind will generally make us able to design a more robust software architecture.

Single Responsibility Principle is deeply related to Coupling and Cohesion, which also relates to the idea of separating concerns (The separation of Concerns).

Uncle Bob states that SRP is the idea in which:

Each software modules should have one and only one reason to change.

Well, I did not grasp it instantly the first time I hear about it. Reason to change? What the heck it's suppose to mean?

Turns out it's not that complicated though. Theoretically.

Single Responsibility Principle is all about people.

Who do you think will likely to care about the method you wrote on class Foo?

Who do you think will likely to request changes on the method you wrote on class Bar?

If more than a single person or -- as stated by Uncle Bob -- a single tightly coupled group of people representing a single narrowly defined business function, it is likely that you violate Single Responsibility Principle!

For instance (example from here):

public class Employee {  
  public Money calculatePay();
  public void save();
  public String reportHours();
}

The financial department will likely to care about calculatePay() method.

The IT department will likely to care about save() method.

The operational department will likely to care about reportHours() method.

If, for instance, financial department request changes on calculatePay() and accidentally breaks reportHours(), whose fault is this!?

If each methods otherwise be separated to it's own class, this sort of accident would not have happened!

Concise Cohesion and Coupling

Cohesion and coupling is rather a common concept among programming languages. It derives several principles such as Single Responsibility Principle.

Cohesion

Cohesion simply means specific on what it's doing, or in other word, focus on one thing only!

It refers to all software entities: modules, classes, functions.

Cohesion is the degree to which elements of a whole belong together. Methods and fields in a single class and classes of a component should have high cohesion. High cohesion in classes and components results in simpler, more easily understandable code structure and design. - Robert C. Martin

Rule of thumb: high cohesion and low coupling.

But you will notice that low coupling and high cohesion are contradict to one and another. When you're trying to decouple an entity, you will find yourself having a low cohesive entity.

On classes or modules, it rather easier to identify either they are doing single or multiple things.

Low Cohesive:

public class Staff() {  
  public void checkEmail() {}
  public void sendEmail() {}
  public void emailValidate() {}
  public void printLetter() {}
}

Staff is responsible on multiple task, which violate cohesiveness.

High Cohesive:

public class Staff() {  
  private double salary;
  public void setSalary() {}
  public double getSalary() {}
  public void printLetter() {}

On a function, it's a bit tricky to see if a function is focused.

Large function that has been extracted to smaller pieces turns into function comprises set of steps.

It might seen as follows:

function foo() {  
  doA();
  doB();
  doC();
}

Clearly the function does three things.

According to Uncle Bob, we can say a function is focused if each steps contains the same level of abstraction.

What it means briefly is that the function has to be equal in terms of implementation detail! For instance, sum('2', '5') is a much lower abstraction level than renderPage(). Fastest way to notice abstraction level of a function is too see LOC (lines of code) it has. It does not matter if the next function also comprises of a set of steps as long as each steps has it's abstraction level equal to it's caller abstraction level.

So continuing the previous example, doA() might look like:

function doA() {  
  doD();
  doE();
  doF();
}

Another trick is, if you can't extract another function from it (nor extracting merely as a restatement*), then your function is focused.

*such as extracting an if statement to it's own function.

A function that receives a flag or ones which has a switch statement are likely to do a lot of things.

function receivesFlag(let isTrue) {  
  if (isTrue) {
    doSomething();
  } else {
    doSomethingElse();
  }
}
function handleCases(let case) {  
  switch(case) {
    case A: {
      doFoo();
    }
    case B: {
      doBar();
    }
  }
}

Coupling

Coupling is a concept for classes, modules, and component.

Two classes, components or modules are coupled when at least one of them uses the other. The less these items know about each other, the looser they are coupled. A component that is only loosely coupled to its environment can be more easily changed or replaced than a strongly coupled component. - Robert C. Martin

It refers to how related classes/modules and how dependent they are on each other, and the difficulty on replacing either entity.

High coupling would make your code being difficult to perform any changes (maintain) as well as being replaceable.

This is due to the fact that when you change your dependencies, it is likely that you need to update code that depends on it.

This is true for me, there are times when I import a community-driven module for my codebase, and when that module was being updated, it breaks my current code, therefore requiring my code to adapt to the latest version.

As for the replaceability issue, your code functionality depends on many of your dependencies. Changing your code functionality as a whole would be difficult without changing the dependencies itself, therefore hard to replace.

Low coupling is the preferred methodology.

Tightly coupled code experience much harder maintenance (because changing your dependencies means changing your code) and difficult to replace.

Some guy on the internet made an analogy on this using iPhone an another typical smartphone:

On an iPhone, if you ever find yourself having a broken battery, the cost to replace the battery is so expensive such that you're better to replace the entire iPhone itself, whereas on other smartphone you can easily replace it with a new battery.