Makers Week 1: Surfing in the coding ocean but I managed to catch some fish

Test-Driven Programming, Debugging, Object-oriented Programming…

Image for post
Image for post
image by Pixabay

London, Shoreditch

The first week at Makers passed so quickly before I even realise it. There is such an information explosion and everyday I was thrown with tons of new stuff.

— Yet, exciting!

However, good thing is that I managed to catch a few big fish in chaos :

  • Object-oriented programming: A few high-level design concepts
  • Test-driven programming: Guidelines, RSPEC (how to set-up and basic syntax), Test doubles (such a powerful tool!)
  • Debugging: Common procedure to debug an error

These 3 points are equally important, because you can almost say that they lay the first brick for your coding career. I do feel, however, understanding OOP (Object-oriented programming) will improve my learning experience with testing, so that’s where I started.

I really like reading the high-level design concepts in OOP. Encapsulation, Single Responsibility Principle, Polymorphism. It helps me to understand the code from others and to design my code before I enter into granular levels. For example. the Single Responsibility Principle helps me to avoid stuffing all the functionality into one single method, which I enjoyed doing previously. For now, I know that I only have a shadow understanding of these design concepts, but it really good to get exposed at early stage of the software engineer career.

When it comes to testing. While I felt comfortable in following the TDD rules(not to write code unless writing a failed test first, only write sufficient code to pass the test), I was struggling to comprehend the concept of test doubles, and subs, mocks following up. And this came hand in hand with reading the Rspec error messages, as I couldn’t come up with a possible solution to fix my stub clause without fully understanding the error message linked with it. At one time, I wrote something like:

However, I didn’t define a double (weather) first using weather = double :weather, or let(:weather){double :weather). Not surprising, the test kept failing!

Debugging is another thing I need to work on more. While it’s seemingly an easy process to follow, you just can’t simply migrate theory into practice. I feel I need to have a dedicated note to log all the error messages I got, and try to learn from that.

In the end, one lesson for the first week :

Everything will have to come down to habits developing. As a coder with less than 100-hour experience, I feel I need to hard-code so many concepts into my code writing. Habits can be as big as applying the debugging procedure properly (God know how struggled i was during the pre-course when my code didn’t work, without any idea of what those error messages meant!). However, habits can be as small as following a best-practice naming convention, such as: Don’t use Magic Numbers unexplained and use constant with a good name implication of the magic number instead.

Ok, see you next week and happy coding!

==========Highlights of the Week=============

OOP is method of computer programming that involves using ‘objects’. An object is essentially a piece of code that lets you create many similar pieces of code without actually re-writing the code each time. This is done by first writing a ‘class’, which is essentially a blueprint of the object, then you can easily create an ‘instance’ of the object, which is basically just one particular version of the object, built from the blueprint.

A class can have both methods (or ‘functions’ — basically, things that every object of that class can do) and properties (see below for examples of properties).

There are 4 major principles that make a language Object Oriented. These are :

  • Abstraction: The process of picking out (abstracting) common features of objects and procedures.
  • Encapsulation: The process of combining elements to create a new entity. A procedure is a type of encapsulation because it combines a series of computer instructions.
  • Inheritance: a feature that represents the “is a” relationship between different classes.
  • Polymorphism: A programming language’s ability to process objects differently depending on their data type or class.

Encapsulation is achieved when each object keeps its state private, inside a class. Other objects don’t have direct access to this state. Instead, they can only call a list of public functions — called methods.

There are a lot of benefits using encapsulation, one of them being data hiding: The user will have no idea about the inner implementation of the class. It will not be visible to the user that how the class is storing values in the variables. He only knows that we are passing the values to a setter method and variables are getting initialised with that value.

Applying abstraction means that each object should only expose a high-level mechanism for using it. This mechanism should hide internal implementation details. It should only reveal operations relevant for the other objects.

The main difference between the above two is that encapsulation hides information from the caller, abstraction hides information from the callee. (Thinking from an engineer coding the software and a real user using the software).

Inheritance is defined as a child class can inherit all the methods from the parent class. This is an IS-A relationship.

Simply put, polymorphism gives a way to use a class exactly like its parent so there’s no confusion with mixing types. But each child class keeps its own methods as they are.

One typical polymorphism in ruby is about Method Overriding defined as follow:

In object oriented programming, method overriding is a language feature in which a subclass can provide an implementation of a method which is already mentioned by its super classes.
Here’s an example:

The method a in class B overrides the method a in class A.

Other than this 4 basic principles, there’re also some classic principles to follow. For example:

This principle is based on the fact that a class should be responsible of only one aspect of a program. By responsible we mean that the class can be impacted by a change in only one aspect of the program

So basically is to make it high cohesion — — for example, to have two smaller classes that handle the two specific tasks. We have our processor that is responsible for processing and our calculator that computes the amount and creates any data associated with our new commission. Instead of mixing calculator into the processor class, so anytime to change the calculator class have to change the processor class again.

The Open/Closed Principle states that classes or methods should be open for extension, but closed for modification.

For example, we have the code below. But since we have hard-coded dependency on EmailSender class. Our code collapse when the EmailSender class change, and we cannot easily extend it.

One way of doing is to use dependency injector to inject sender into that method:

Because NotificationSender is accepting any sender, now we can easily extend functionality by new type of sender.

In this case if user is active he will receive SMS. If we want to add other sort of notification, all we need to do is to create class, inherit it from Sender and implement send method which would accept user and message.

We can extend functionality without changing implementation of NotificationSender.

(LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping. It’s like “if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.

Programming languages with strong typing have less chances to break Liskov substitution principle. Because they strictly define types of method arguments and returning values.

In case of Ruby we are responsible for that. We can easily break Liskov substitution principle by changing returning type and that would make substitution impossible:

Now HomoSapiens returns hash instead of string for height. That might break code that expects string from height method call.

There is also Interface Segregation Principle and Dependency Inversion Principle mentioned in the class, however, I decided to not to go into too much details this week as it’s quite a lot for me just to digest all the other principles above.

Test-Driven Programming:

So what is a test? Or what does test mean in coding?

The Four-Phase Test is a testing pattern, applicable to all programming languages and unit tests (not so much integration tests).

It takes the following general form:

The AAA (Arrange-Act-Assert) pattern has become almost a standard across the industry. It suggests that you should divide your test method into three sections: arrange, act and assert.

So the arrange section you only have code required to setup that specific test. Here objects would be created, mocks setup (if you are using one) and potentially expectations would be set. Then there is the Act, which should be the invocation of the method being tested. And on Assert you would simply check whether the expectations were met. For example:

Unit tests tell a developer that the code is doing things right; functional tests tell a developer that the code is doing the right things.

Unit Test — testing an individual unit, such as a method (function) in a class, with all dependencies mocked up.

Functional Test — AKA Integration Test, testing a slice of functionality in a system. This will test many methods and may interact with dependencies like Databases or Web Services.

Uncle Bob describes TDD with three rules:

  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

Read them carefully and you’ll notice they’re simpler than they look like, and it’s obvious now that rule 3 implies rule 1, so here’s a concise version that I’ve found easier to remember:

  1. Write only enough of a unit test to fail.
  2. Write only enough production code to make the failing unit test pass.

Sometimes we might go into too much details in the testing, and end up repeating the code in the test subject line by line. We might try something like this:

What we want to do is to test behaviour not state (the exact value being output)

Follow this process:

  1. Who or what is the user of this code? It might be the end user (a real person), or it might be another class
  2. What job is this code doing for that user?

For example:

  • Question: Who, or what, is the user of this code?
    Answer: The user of my Diary class is a person who is keeping their diary
  • Question: What job is this code doing for that user?
    Answer: They add an entry so they can read it later

That second answer is the job or behaviour of the Diary class.

A test double is a simplified version of an object that allows us to define “fake” methods and their return values. There are couple of different variations:

Instead of testing ObjectA against an instance of ObjectB, I use a stand in (a stuntman if you like) for ObjectB instead. ObjectA doesn't know the difference, it simply treats the double as if were an instance of ObjectB, but it's not - it's a dummy that I've set up with static (and therefore not variable) values.

I want to test some behaviour of ObjectA, but during the execution of that behaviour, ObjectA calls a method on ObjectB. I don't need to test that the method on ObjectB gets called, but I do want to make sure that when it does, the method on ObjectB always returns a specific value.

I want to test some behaviour of ObjectA and, critically, during the execution of that behaviour, ObjectA must call a method on ObjectB with specific arguments. In my test, I don't particularly care what happens afterwards, but I want to test that in the code that is about to be executed the specific method is called with the correct arguments.

I want to test some behaviour of ObjectA and, critially, during the execution of that behaviour, ObjectA must call a method on ObjectB with specific arguments (sound familiar?). In my test, I don't particularly care what else happened during the test, just that in the code that was just executed the specific method was called with the correct arguments.

Debugging

  • Tighten the loop (find the exact line the bug is coming from, and the file)
  • Get visibility (e.g. use p to understand what is happening, read the stack trace)
  • Get a hypnosis
  • Implement the hypnosis
  • Run the test with error message in isolation, if it’s not working, roll back

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store