Turning OOP class to graph for better code composition and understanding
Created:28 Jul 2020 00:28:15 , in Maths, Web development
Comprehending code is often a hard and time-consuming task. It becomes even harder when a piece of code your are dealing with is a large class with a sizeable number of properties and methods. Level complexity grows even further when object based on a class starts calling various methods on itself or interacts with other objects. The latter usually involves method compositions, or hundreds or thousands of calls one after the other. The simplest cases excluded, only computers can follow in what state objects are after each function call. So, is there a way for a human, to follow all these changes in a somewhat organized manner?
In this article I look at one method which I have developed for my own purpose of tracking, visualize and quicker and better understanding changes in a dynamic system based on object oriented programming principles. It is a powerful tool while compared with a unit testing framework like PHPUnit or similar.
Properties and methods as building blocks for a graph
Programmers write object oriented code in a hundred of ways. My method is to primarily focus on properties and methods in my classes. Everything else is of secondary importance. The reason is, I want to treat each class as a blueprint for an object which CAN be EASILY represented as a graph. In the graph the whole set of properties of my object becomes a vertex. Methods of the object are means which enable transition to a new state. They are edges of the graph. They take me from a vertex to a different one. Properties are allowed to change only through a method call.
Advantages of representing code as a graph
Once a piece of code is represented as (directed) graph you can employ the whole machinery of graph and set theory to deal with complexity your it introduces. For example, you can find all the possible object states and compositions of methods which lead to them. You can also automatically build suites of tests only for possible states of the object based on the set of methods it has and how they change the object's internal state. Last but not least, graphs lend themselves for by-hand analysis and visualization using a pen and a sheet of paper too. If a graph, which represents a class, does not fit on a sheet of paper, you have likely put too much logic in your piece of code. Time to refactor. After all, the best graphs are those which are small and simple and for which recursive methods are easy to write.
Tracking object state changes
One practical point about being able to actually track what's going on within our object as you call its methods is a good 'inspect' method. One which I use returns all the properties of the object, that is, its whole state at the given time of execution. It does not matter whether it is in the same class or not, as long as it has access to all the properties and does not change any of them as it is called.
A practical example of class as a graph in PHP
Let's say you want to build a Kettle class. An electric kettle is a fairly simple, specialized object, which all of us have used at least once. It has a container for water, a lid, a boil button, sometimes also switch on / off button and obviously can boil water. The following code is one such class written in PHP programing language:
class Kettle{
private $on = false;
private $hasWater = false;
private $lidOpen = false;
private $isBoiled = false;
private $water = NULL;
public function __construct(){}
public function __get($property){
return $this -> {$property};
}
// if not on switch kettle on
public function switchOn(){
if(!$this -> on){
$this -> on = true;
}
return $this;
}
// if not off switch kettle off
public function switchOff(){
if($this -> on){
$this -> on = false;
}
return $this;
}
// open lid if not open
public function openLid(){
if(!$this -> lidOpen){
$this -> lidOpen = true;
}
return $this;
}
// close lid if not closed
public function closeLid(){
if($this -> lidOpen){
$this -> lidOpen = false;
}
return $this;
}
// add water
// pass water object by reference so that it can be updated
public function fill(&$water){
$this -> openLid();
if($this -> hasWater){
throw new ErrorException('No room for more water in the kettle.');
}
$this -> water = $water;
$this -> hasWater = true;
$this -> closeLid();
return $this;
}
// remove water
public function clear(){
$water = NULL;
$this -> openLid();
if(!$this -> hasWater){
throw new ErrorException('There is no water in the kettle.');
}
$water = $this -> water;
$this -> hasWater = false;
$this -> isBoiled = false;
$this -> water = NULL;
$this -> closeLid();
return $water;
}
// boil water
public function boil(){
// if not on, err
if(!$this -> on){
throw new ErrorException('Kettle is not on!');
}
// if kettle has water
if($this -> hasWater){
// if lid open
if($this -> lidOpen){
throw new ErrorException('The lid is open. Close it and try again!');
// if has water and lid closed
} else {
// 100 deg Celcius
$this -> water -> temperature = 100;
$this -> isBoiled = true;
$this -> switchOff();
}
// if no water inside
} else {
throw new ErrorException('No water in the kettle. Add water first');
}
return $this;
}
// inspect state of the kettle
public function inspect(){
return [
'on' => $this -> on,
'hasWater' => $this -> hasWater,
'lidOpen' => $this -> lidOpen,
'isBoiled' => $this -> isBoiled,
'water' => $this -> water,
];
}
}
Kettle class is not the smallest of classes. I intentionally made it larger than perhaps it should be. From the point of view of turning the class to a graph only a fraction of the code is required:
Set of properties looks as follows:
{ on,hasWater,lidOpen,isBoiled,water }
Set of methods is:
{ switchOn,switchOff,openLid,closeLid,fill,clear,boil,inspect }
A vertex represents a state of the object at a given time. It is made up of a particular combination of all properties and their values in the object at that time.
An edge is a method object has.
Entry vertex for a graph representing the kettle is:
on => false,
hasWater => false,
lidOpen => false,
isBoiled => false,
water => NULL
Using swtichOn edge we move to state which is represented by a vertex like this:
on => true,
hasWater => false,
lidOpen => false,
isBoiled => false,
water => NULL
openLid edge takes us to:
on => true,
hasWater => false,
lidOpen => true,
isBoiled => false,
water' => NULL
and so on.
If you want to boil water before you have filled the kettle. An exception will be thrown. It will tell you that there is no direct connection between the two edges representing entry and boil state.
However boil state can be achieved indirectly, using a few edges in a particular order: switchOn, fill and boil.
Here is a transition from the initial state where the kettle is switched off to when it has boiled water and got emptied:
// water is an object as follows:
$water = (object) array(
'volume' => '2 litres',
'temperature' => 20
);
$kettle = new Kettle;
$kettle -> switchOn();
$kettle -> fill($water);
$kettle -> boil();
$output = $kettle -> inspect();
// expected state after water was boiled
$expected = [
'on' => false,
'hasWater' => true,
'lidOpen' => false,
'isBoiled' => true,
'water' => $water
];
// change state using clear method
$water = $kettle -> clear();
$output = $kettle -> inspect();
// expected state after water was removed
$expected = [
'on' => false,
'hasWater' => false,
'lidOpen' => false,
'isBoiled' => false,
'water' => NULL
];
If you inspect the water object you will notice that its temperature property holds value 100 now. Now turn it to a graph with initial and after-boiled states. You will notice that there is no edge (a method in the object) to change state of the object like that. So, this should not be allowed. In general changing properties of object should not be allowed directly. The method was omitted for the sake of simplicity simplicity.
Applying principles to functions and variables
The method described here for classes can be used for functions and their variables too. Variables and operations in the function correspond to properties and methods respectively then. Obviously, as number of variables and methods grows number of states the function can be in grows too. Hence, this method relies on writing short and single-purpose functions. Exactly what any good programmer would tell you.
Conclusion
The idea described in this article is loosely based on what's known as Finite State Automata and has proven very useful to me when trying to understand other people's code. It is an invaluable tool when refactoring and testing code too, especially if combined with testing framework like PHPUnit or Mocha + Chai for Nodejs.
I hope you have found this article useful. If you have something to add, leave a comment below.
This post was updated on 28 Jul 2020 01:31:06
Author, Copyright and citation
Author
Author of the this article - Sylwester Wojnowski - is a sWWW web developer. He has been writing computer code for the websites and web applications since 1998.
Copyrights
©Copyright, 2024 Sylwester Wojnowski. This article may not be reproduced or published as a whole or in parts without permission from the author. If you share it, please give author credit and do not remove embedded links.
Computer code, if present in the article, is excluded from the above and licensed under GPLv3.
Citation
Cite this article as:
Wojnowski, Sylwester. "Turning OOP class to graph for better code composition and understanding." From sWWW - Code For The Web . https://swww.com.pl//main/index/turning-oop-class-to-graph-for-better-code-composition-and-understanding
Add Comment