FULLTEXT01
FULLTEXT01
Examensarbete 30 hp
September 2021
Matilda Trodin
2 Related theory 12
2.1 Object-Oriented Programming and its characteristics . . . . . . . 12
2.1.1 Object and class . . . . . . . . . . . . . . . . . . . . . . . 12
2.1.2 Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.1.3 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.4 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.5 Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 SOLID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2.1 Single Responsibility Principle - SRP . . . . . . . . . . . . 14
2.2.2 Open Closed Principle - OCP . . . . . . . . . . . . . . . . 14
2.2.3 Liskov Substitution Principle - LSP . . . . . . . . . . . . 17
2.2.4 Interface Segregation Principle - ISP . . . . . . . . . . . . 18
2.2.5 Dependency Inversion Principle - DIP . . . . . . . . . . . 19
2.3 JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.1 Object and Class . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.2 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.3 Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3.4 Is JavaScript an object-oriented language? . . . . . . . . . 22
2.4 React . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4.1 Virtual DOM . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4.2 JSX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4.3 Components, props and states . . . . . . . . . . . . . . . 23
2.4.4 Composition vs Inheritance . . . . . . . . . . . . . . . . . 24
5
6 The original application 33
6.1 File structure and descriptions . . . . . . . . . . . . . . . . . . . 33
6.1.1 App.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.1.2 Display.js . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.1.3 ButtonPanel.js . . . . . . . . . . . . . . . . . . . . . . . . 34
6.1.4 Button.js . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.1.5 calculate.js . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.1.6 operate.js . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.1.7 isNumber.js . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6
8.3.22 handleOperationInput.js . . . . . . . . . . . . . . . . . . . 44
12 Discussion 57
12.1 The rewritten application . . . . . . . . . . . . . . . . . . . . . . 57
12.2 The SOLID analysis . . . . . . . . . . . . . . . . . . . . . . . . . 57
12.2.1 Single Responsibility Principle . . . . . . . . . . . . . . . 57
12.2.2 Open Closed Principle . . . . . . . . . . . . . . . . . . . . 58
12.2.3 Liskov Substitution Principle . . . . . . . . . . . . . . . . 58
12.2.4 Interface Segregation Principle . . . . . . . . . . . . . . . 58
12.2.5 Dependency Inversion Principle . . . . . . . . . . . . . . . 59
12.3 Code metrics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
7
12.3.1 Weighted Method per Class . . . . . . . . . . . . . . . . . 59
12.3.2 Depth of Inheritance Tree . . . . . . . . . . . . . . . . . . 59
12.3.3 Number Of Children . . . . . . . . . . . . . . . . . . . . . 59
12.3.4 Coupling Between Objects . . . . . . . . . . . . . . . . . . 60
12.3.5 Reference For a Class . . . . . . . . . . . . . . . . . . . . 60
12.3.6 Lack of Cohesion in Methods . . . . . . . . . . . . . . . . 60
12.4 Expandability and maintenance . . . . . . . . . . . . . . . . . . . 60
13 Conclusion 62
14 Future Work 63
References 64
8
C Appendix - Changes made for expandability and maintenance
analysis 81
C.1 Changes in code - Original application . . . . . . . . . . . . . . . 81
C.2 Changes in code - Rewritten application . . . . . . . . . . . . . . 82
9
1 Introduction
React is a fast-growing and popular JavaScript library for building user inter-
faces, originally released by the developers at Facebook in 2013. JavaScript
itself is a multi-purpose language able to adapt to many di↵erent development
styles and practices. The release of the ES6 version of JavaScript in 2015 in-
troduced a new syntax for classes, commonly used in object-oriented program-
ming, but also frequently used in React. Does this mean that JavaScript is an
object-oriented language? It’s a debatable question, but one way to explore
the object-oriented possibilities of JavaScript, and in extension React, would
be to apply object-oriented design principles to JavaScript code and analyse
the outcome. Object-oriented languages have been around for a long time and
there are many potential practices and principles to explore. One of these are
the five SOLID principles, well-established in the object-oriented development
community. The principles promote clean and clear code that is easy to scale
and maintain, positive attributes in any software system.
1.1 Goal
By looking at React from an object-oriented point of view and incorporating the
SOLID design principles, this project seeks to challenge the React way of think-
ing to further explore the possibilities of this popular and fast-growing library.
By rewriting an existing React application and incorporating the SOLID princi-
ples, I want to explore if they make the React code better, worse, or something
in between. This will be established with di↵erent analysis approaches, looking
at both measurable metrics and more observable values such as maintenance
and scalability. The project should answer the following questions:
1. How well does the rewritten application follow the SOLID principles com-
pared to the original?
2. Are any of the SOLID principles simply not possible to follow when using
React?
3. In terms of the goals of the SOLID principles, is the code better in the
rewritten application now that it follows them?
4. Are there any measurable values that can indicate improvement now that
the rewritten application follows the SOLID principles?
10
literature that gives examples on how to combine the SOLID principles with
JavaScript, for example Mastering JavaScript Object-Oriented Programming by
Andrea Chiarelli, but not with React specifically. This project is thereby a good
contribution to this relatively unexplored topic.
11
2 Related theory
This section covers the main subjects we need to understand for this project,
starting with object-oriented programming in general, moving on to the SOLID
principles, JavaScript, and finally React. The section explains programming
language fundamentals as well as more complex concepts with code examples
to easier understand them.
2.1.2 Abstraction
Abstraction is the concept of shifting focus from the details of an implementation
to the features relevant to understand it [4, 1, 21]. Abstraction is fundamental
when dealing with complexity and helps us define characteristics that distinguish
an object from another in relation to the viewer [3]. Dealing with complexity is
one of the main purposes of OOP, which is why abstraction is such an essential
characteristic of OOP [4].
12
2.1.3 Encapsulation
Encapsulation is closely related to abstraction since it’s the mechanism that
makes it possible to hide the detailed implementation of an object [3, 4]. En-
capsulation enables restriction to certain data or behavior inside our classes
and objects [21, 1], shielding our implementations and creating interfaces that
enclose our abstractions of the implementations [3].
2.1.4 Inheritance
According to an article written by Deborah J. Armstrong, inheritance is the
most common keyword mentioned in publications related to OOP develop-
ment [1] and when used correctly it can be a very essential concept for OOP
developers [1, 17]. Inheritance lets you define relationships between classes
by creating class hierarchies where the subclasses inherits the structure or be-
haviour defined in one or more classes higher in the hierarchy, called a super-
class [3]. The subclass can then, if needed, redefine the inherited definitions to
make them more specialised and specific to its purpose [3]. For example if a
super-class is defined as Mammal, a subclass of that might be Dog. Out of that
subclass we can then create an even more specific subclass, for example Poodle.
Inheritance is an especially important tool for reducing redundant code and
for promoting reusability, thereby saving time since you don’t have to re-write
code that has already been written [18, 21].
2.1.5 Interface
Interface is a term that can mean several things in computer science. In the
context of this project, an interface can be defined as an abstraction of a software
component that specifies what the component does without specifying how [2,
3, 4]. An interface could be viewed as a contract that the software component
o↵ers other components interacting with it, creating a shared boundary where
they can interact and share information [3, 4].
In object-oriented languages like Java or TypeScript, there is a built-in ref-
erence type [16] called interface that makes it easy to create contracts between
software components such as classes. In Section 2.3.3 we will explore how we
can establish this contract in JavaScript, a language that does not support this
data structure.
2.2 SOLID
The five SOLID design principles have long been considered good practices in
object-oriented development, mainly referenced in an object-oriented context
with design practices for classes and interfaces [15], however their application is
not limited to object-oriented design [4]. The principles have di↵erent origins,
but it was Robert C. Martin who conceptualised them in the early 2000s [15,
11]. The goal of following the principles are mainly to make code more flexible
13
and robust, preparing it for future changes and making it easier to maintain
and expand [15, 4].
The principle doesn’t mean that a class can only do one thing, but rather it
means that everything the class does should be related to its responsibility. If a
class has more than one responsibility, the responsibilities become coupled and
changes to one of them may make the class unable to live up to the other [11].
This can lead to fragile classes that break unexpectedly when modified [11].
The Single Responsibility Principle is great for creating clear and logical classes
that each have a single responsibility, which also makes it easier to maintain
and scale [4, 15]. Except for classes, this principle can also include functions,
models, and other data structures [10, 4, 15].
We can see an example of this in figure 1. The class Calculator contains
two methods: add and subtract. This class clearly violates the principle since
it has two responsibilities. To the right the class has instead been split into two
separate classes with each one having one responsibility, now following the SRP.
Figure 1: To the left we see a class that has two responsibilities, violating the
Single Responsibility Principle. To the right the class has been split into two
di↵erent classes with individual responsibilities, now following the principle.
What does this contradiction mean? Bertrand Meyer, who first introduced
this principle, explains that a module is open if you can extend it and closed if
another module can use it. Martin adapts this definition in his book by claiming
that:
Software entities (classes, modules, functions, etc.) should be open
for extension, but closed for modification.
14
An extension could for example be to add new data sets or add new operations
to a module [14]. A modification could for example be to change an existing
operation of a module [11].
The goal of keeping our modules open is to prepare them for future changes,
making them flexible and easy to extend [14]. The goal of keeping our modules
closed is to make sure we don’t break any dependencies between our modules
when we extend them [11, 15]. If we for example change an existing operation
inside a module to adapt to another module, other modules who are dependent
on this operation may break. When this principle is invoked, applications are
easier to maintain, easier to reuse and more robust [11].
In figure 2 and listing 1 we can see an example of how the Open Closed
Principle could be used when building an area calculator for geometric shapes.
In the left version of the calculator, the class AreaCalculator has to adjust its
calculateArea method according to each type of shape, since not all shapes
have the same disposition. If we would like to add a new type of shape, we would
have to both add a new class for the shape, as well as edit the AreaCalculator
class. In the new version, we would only have to add a new class for the shape
since the method for calculating the area for each individual shape resides in its
respective shape class. The AreaCalculator is then dependent on the interface
Shape and can access this method inside each shape in order to perform its
calculation.
Figure 2: Illustrating the code in listing 1. To the left we see the part of the
code violating the OCP where the AreaCalculator class has to adjust everytime
a new shape class is added. To the right we can see the part of the code following
the OCP where the AreaCalculator class is instead dependent on an interface
that makes sure each shape class has a method for calculating its own area. The
AreaCalculator class can access these methods and get the area of each shape
in order to perform its own calculations.
15
1 // Not following the OCP
2
3 public class Rectangle {
4 private double length ;
5 private double height ;
6 }
7 public class Circle {
8 private double radius ;
9 }
10 public class AreaCalc ulator {
11 public double calculateArea ( ArrayList < Object > ... shapes ) {
12 double area = 0;
13 for ( Object shape : shapes ) {
14 if ( shape instanceof Rectangle ) {
15 Rectangle r = ( Rectangle ) shape ;
16 area += ( r . getLength () * r . getHeight () ) ;
17 } else if ( shape instanceof Circle ) {
18 Circle c = ( Circle ) shape ;
19 area += ( c . getRadius () * c . getRadius () * Math . PI ) ;
20 } else {
21 throw new R u n t i m e E x c e p t i o n ( " Shape not supported " ) ;
22 }
23 }
24 }
25 }
26
27 // Following the OCP
28
29 public interface Shape {
30 double getArea () ;
31 }
32 public class Rectangle extends Shape {
33 private double length ;
34 private double height ;
35
36 @Override
37 public double getArea () {
38 return ( length * height ) ;
39 }
40 }
41 public class Circle extends Shape {
42 private double radius ;
43
44 @Override
45 public double getArea () {
46 return ( radius * radius * Math . PI ) ;
47 }
48 }
49 public class AreaCalc ulator {
50 public double calculateArea ( ArrayList < Shape > shapes ) {
51 double area = 0;
52 for ( Shape shape : shapes ) {
53 area += shape . getArea () ;
54 }
55 return area ;
56 }
16
57 }
Listing 1: Code example illustrating the Open Closed Principle with code
violating the principle and rewritten code adhering to it
17
Figure 3: Example illustrating the Liskov Substitution Principle. To the left we
can see a Register class with many defined methods that may not be relevant
to each child class that is derived from it. The derived child classes therefore
have to override their implementations which violates the LSP. To the right the
methods have been moved into the specified child classes, better adhering to the
LSP.
This principle is focused on how to create proper interfaces, stating that you
should only make interfaces that are specific and make sense for the client that is
using the interface [4, 15]. This means that clients should not be dependent on
methods and functionalities that it does not use, and we avoid this by defining
our interfaces with only that which is necessary. This principle is similar to the
Single Responsibility Principle as it promotes simplification and leads to smaller
interfaces that are easier to maintain [4, 15]. This principle is not limited to the
data structure interface, however, but rather it can reference the entire set of
public methods of an object [4].
In figure 4 we can see an example of the Interface Segregation Principle and
how it can be applied to the classes we saw in figure 3.
18
Figure 4: Example illustrating the Interface Segregation Principle. To the left
we can see an interface with many methods that may not be relevant to each
class that uses the interface. To the right the methods has been separated into
more specific interfaces that adheres better to each of their purposes.
19
Figure 5: Example illustrating the Dependency Inversion Principle. To the left
we can see that the class Project is directly dependent on the specific imple-
mentations in the FrontEndDeveloper- and BackEndDeveloper classes. To the
right, the classes are instead both dependent on an abstraction, the interface
Developer.
20
2.3 JavaScript
Despite its name, JavaScript is very di↵erent from the Java programming lan-
guage. JavaScript originated as a scripting language, but with its continuous de-
velopment, it has grown to a multi-purpose language that can be used with both
functional- and object-oriented programming styles [6]. According to the Stack
Overflow developer survey of 2020, taken by over 65,000 developers, JavaScript
is the most common programming language used by developers and has been
for the last eight years [22].
2.3.2 Inheritance
When ES6 introduced the class construct in JavaScript it also introduced super-
classes and subclasses, which means that it’s possible to create classes that
inherit from other classes just as it’s done in OOP-languages [4, 6]. However,
JavaScript had support for inheritance even before ES6 and the class construct
was introduced. Since JavaScript uses prototype-based inheritance, this means
that we can create inheritance by assigning the same prototype to di↵erent
objects [4]. This set of objects that inherits from the same prototype then
represents a class [6].
2.3.3 Interface
As mentioned in Section 2.1.5 there is a built-in abstract type called interface
in some object-oriented languages. In this project we are working in JavaScript
which does not support this, however since the ES6 release of JavaScript it does
support classes [4, 6] which means we might have use of interfaces as well.
Interfaces are also a big part of the SOLID principles, especially the Interface
Segregation Principle. We have to consider the definition of an interface again:
a contract that states what a class does without stating how. Is there a way we
can express this contract in JavaScript?
The short answer is yes, we can. One possible approach is to create a super-
class containing methods that cause the program to fail unless the derived class
21
overrides them, forcing the derived classes to implement the methods defined in
the super-class. If a derived class has not implemented the desired methods, the
program will issue an error and the program will fail. The ”interface” makes
sure that all classes who are derived from it implement the methods that the
”interface” promises. This technique is derived from the book JavaScript: The
Definitive Guide by David Flanagan and in listing 2 we can see an example of
this that gives us the contract we were looking for.
1 class An im a lI nt er f ac e {
2
3 name = () = > {
4 throw new TypeError (
5 this . constructor . name + ’ must implement the name
method ! ’
6 );
7 }
8
9 animalSound = () = > {
10 throw new TypeError (
11 this . constructor . name + ’ must implement the
animalSound method ! ’
12 );
13 }
14 }
15
16 class Dog extends An i ma lI nt e rf ac e {
17
18 name = () = > {
19 console . log ( " The dogs name is : Buster " ) ;
20 }
21
22 animalSound = () = > {
23 console . log ( " The dog says : woof " ) ;
24 }
25 }
Listing 2: Code example illustrating how an interface could be created in
JavaScript.
22
Whether or not JavaScript is an object-oriented language, its di↵erence to
more classical object-oriented languages could potentially raise some interesting
questions regarding the application of the SOLID principles with JavaScript.
2.4 React
React is a JavaScript library created by Facebook, mainly used for developing
user interfaces such as websites and mobile applications [20, 19]. According to
the Stack Overflow developer survey of 2020, taken by over 65,000 developers,
React is the second most loved and first most wanted web library to work
with [22]. To understand why React is so popular we have to get familiar with
the most fundamental React concepts.
2.4.2 JSX
JSX is a syntax extension to JavaScript recommended when using React. Usu-
ally, when designing for the web, you keep your markup, HTML, and logic,
JavaScript, in separate files. With JSX you can combine these two and couple
them into the same file called a component [19], which will be further explained
in the following section.
23
2.4.4 Composition vs Inheritance
As mentioned in Section 2.3.2, inheritance is strongly related to object-oriented
languages. React uses classes similar to object-oriented languages, however,
they don’t recommend using inheritance [19]. As stated on their web page:
24
3 Measuring code quality
How do we measure quality in code? As a developer, it’s easy to think of quality
from a very subjective point of view. Most developers have their interpretation
of good and bad code derived from their own experiences and preferences. To
avoid a very subjective analysis, this project has chosen to incorporate a metrics
suite from an article published by the authors Shyam R Chidamber and Chris
F Kemerer in 1994 [5]. The suite is established as one of the best-known metric
suites for object-oriented software [8] and is still relevant today, almost 30 years
later. The suite mainly focuses on di↵erent metrics when it comes to classes and
inheritance, which as defined in earlier sections are key terms when it comes to
OOP.
3.1 Metrics
The Chidamber and Kemerer metrics suite define six di↵erent metrics and the
definitions in the following sections are taken directly from the original article.
Some of them have mathematical definitions, and some of them have explanatory
definitions.
25
meaning every exit node in the program is connected back to the entry node.
The cyclomatic complexity CC is then defined as:
CC = e n + p [12]
CC = e n + 2p [12]
The ”2” in the above formula represents the adding of an extra virtual edge
from the exit node to the entry node of each connected component graph [23].
The definitions suggest that cyclomatic complexity mainly measures the ra-
tio between nodes and edges in a program. The more paths we can take de-
pending on conditions in our nodes, the higher the complexity of that program
becomes.
26
Now that we have a definition for measuring complexity in a program we
can move on to the definition of WMC.
Definition: Consider a class C1 with methods {M1 , ...Mn } that are defined in
the class. Let c1 , ...cn be the complexity of the methods. Then:
n
X
WMC = ci
i=1
This metric indicates complexity by showing the depth of our class hierarchies.
When our inheritance trees become deeper, the potential for inherited meth-
ods and variables becomes greater, making the class more complex and unpre-
dictable. Therefore deeper trees can indicate greater design complexity which
can be harder to maintain, test, and develop.
This metric equals the number of child classes derived from a parent class.
If you look at DIT to measure depth, NOC measures width. A great number of
children means great reuse, however, it can also indicate improper abstraction
and misuse of subclassing. NOC can also give an indicator of the influence a
class has on the general design. A high NOC has been related to fewer faults,
however, a class with both high NOC and WMC can indicate high complexity
at the top of the class hierarchy. This also means that a class with a large
number of children may require a wider range of tests of its methods because of
its complexity.
27
To summarise, if done properly high NOC can be desirable at the top of the
class hierarchy, but not lower down.
Coupled means that one class uses methods or instance variables declared in
another class, and the relationship can go both ways. The Chidamber and Ke-
merer article states that independent classes are preferable to dependent classes,
meaning you should keep your CBO count low. Avoiding excessive coupling is
also good for improving encapsulation, reuse, and modularity, all important fea-
tures of object-oriented languages as mentioned in earlier sections. High CBO
can also make maintenance more difficult since the classes are more sensitive to
changes.
RS = M [(all i) Ri
RF C = |RS| [5]
28
Figure 7: Example illustrating how the Response For a Class metric can be
used. This Settings class has two directly callable methods and two indirectly
callable methods, adding the RFC count up to 4 for the class.
Cohesive methods are defined as pairs of methods whose joint sets of instance
variables are non-empty. The LCOM metric takes the number of method pairs in
a class whose joint set of instance variables are empty and subtracts the number
of method pairs that are non-empty. The result can either imply cohesion or
lack of cohesion in the class methods. If a class has a high count of LCOM
it implies that a class probably should be divided into several subclasses that
are more cohesive. Low LCOM decreases complexity and thereby reduces the
probability of errors and flaws in the design and development process.
29
variables used by the method M1 and so on.
The similarity of two methods M1 and M2 is thereby given by:
1 () = I1 \ I2
which in this case would be non-zero. The instance variables of method M3 share
no entities with the sets of instance variables of the other methods, meaning that
the intersection between them would be two null sets. If P is the number of
empty sets, and Q is the amount of non-empty sets, the LCOM will be given
by:
LCOM = |P | |Q| = 1
30
4 Summary of preferred code attributes
As mentioned in section 2.2, the goal of the SOLID principles is mainly to
make code more flexible, robust and prepared for future changes, promoting
maintainability and expandability. If we combine this with the goals of the
Chidamber and Kemerer metrics suite from section 3, we can see a pattern of
preferable attributes that says code should:
• Be easy to maintain
• Be easy to expand
• Avoid complexity
• Avoid excessive coupling
• Promote reuse
• Avoid duplicate code
• Have small and cohesive modules, functions, classes etc.
Note: Some of these attributes somewhat overlap or one leads to the other,
for example by having small and cohesive functions we can also avoid complexity.
Support for these attributes can also be found by looking at other sources
relating to good code practices. The book The Pragmatic Programmer: From
Journeyman to Master [7] by David Thomas and Andrew Hunt (1999) contains
many suggestions and guidelines for software development. The book states
that by making code easy to reuse, you avoid unnecessary duplication of code.
It also states that we want to design our code to be independent with a single
well-defined purpose, reducing complexity and coupled and dependent code that
is harder to maintain and expand.
Steve McConnell (1993) also brings light to these attributes by listing his
”desirable characteristics of a design” in his book Code Complete [13]. He
states that software design should strive for good extensibility, reusability, easy
maintenance, avoid coupling and minimal complexity. He also brings up other
related attributes like making your software portable and lean (not containing
unnecessary parts).
Robert C Martin (2009), who is frequently mentioned in section 2.2 about
the SOLID principles, also brings up these attributes in his book Clean Code:
A Handbook of Agile Software Craftsmanship [10].
Of course, these books mentions several other practices and attributes that
makes good code, as well as circumstances that creates good software and pro-
grammers. I won’t list them all here in this report, however they could be
considered more or less included, since attributes like low complexity, good
maintenance and expandability are frequently referred to as e↵ects of the good
code practises mentioned throughout these books.
31
5 Approach to analysis and measurements
The first step of this project analyses an application written in React by the
React community. It should be determined whether or not the code violates the
SOLID principles and specify which principles and where.
The next step is to rewrite the code in order to fix the identified violations,
meanwhile analysing how applicable the principles are with React. The two
application versions will hereinafter be referred to as The original application
and the The rewritten application.
The third step is to compare the original application to the rewritten appli-
cation. The analysis should contain both objective values of measurement and
discussions on code quality.
Like specified in section 1.1, I want to answer the following questions:
1. How well does the rewritten application follow the SOLID principles com-
pared to the original?
2. Are any of the SOLID principles simply not possible to follow when using
React?
JavaScript is not always considered a pure object-oriented language which
means there might be some limitations to applying the SOLID principles
that are usually applied with object-oriented programming. Analysing
the application of the SOLID principles in the React context is therefore
important.
3. In terms of the goals of the SOLID principles, is the code better in the
rewritten application now that it follows them?
The SOLID principles promote code that is flexible, robust, and easy to
maintain and expand. I want to evaluate if these attributes have improved
in the rewritten application. In order to do this, I will remove an old
feature and add a new feature to the application in both the original and
the rewritten version. The analysis will focus on the complexity of making
these changes, taking into account the amount of code that needs to be
added and changed in each application version.
4. Are there any measurable values that can indicate improvement now that
the rewritten application follows the SOLID principles?
As mentioned in section 3 about measuring code quality, I want to avoid
a subjective quality analysis of code. Therefore in addition to evaluat-
ing expandability and maintenance quality, the two application versions
will also be evaluated according to the Chidamber and Kemerer object-
oriented metrics suite. I will look at both high, low, average, and median
metric results and discuss what the values represent.
32
6 The original application
The original application chosen for the project is a React version of the calculator
used in iOS, the operating system for Apple’s iPhone. The original application is
displayed on the React homepage as an example of a React application developed
without any third-party state management libraries. This application is suitable
for this project because of its size and complexity. It’s also developed by someone
other than the author of this report, making it easier to deliver an objective
analysis.
6.1.1 App.js
App is the parent component for the entire application. It displays the interface
and keeps track of the current state of the application. Its child components are
Display and ButtonPanel. It has one method called handleClick that calls
on the function calculate to execute the next step of the calculation. The
33
method is triggered by its child component ButtonPanel every time a button
on the button panel is clicked. It has three states, total, next and operation
that together represent the current state of the application.
6.1.2 Display.js
This component builds the interface for the display. It accepts the prop value
from its parent component and uses that to represent the value on the display.
6.1.3 ButtonPanel.js
This component builds the interface for the button panel. It accepts the prop
clickHandler. It has one method called handleClick that uses the clickHan-
dler prop to trigger the method handleClick in its parent component App.
The method is triggered by its child component Button every time a button is
clicked.
6.1.4 Button.js
This component builds the interface for a button. It accepts the prop clickHandler.
It has one method called handleClick. The method is triggered every time a
button is clicked, using the clickHandler prop to trigger the method handleClick
in its parent component ButtonPanel. It accepts three other props as well, name,
orange and wide that declares the name and design of the button.
6.1.5 calculate.js
This function interprets a button input and updates the current state of the
calculation in App depending on the button. It accepts two arguments, obj
which is the object for the current state of the calculation, and buttonName
which is the name of the button that has been pressed. It returns the updated
state object. It calls two other functions, operate and isNumber.
6.1.6 operate.js
This function executes mathematical operations and returns the results. It
accepts three arguments, numberOne which is the first number for the operation,
numberTwo which is the second number for the operation, and operation which
states what operation should be executed. It returns a string with the results
of the operation or casts an error if the operation is unknown to the function.
6.1.7 isNumber.js
This is a simple function that checks if a variable is a number. It returns a
boolean value.
34
7 SOLID analysis of the original application
This section analyses how well the original application, displayed in Appendix
A and described in section 6, follows the SOLID principles. Empty fields mean
that the principle does not apply to that entity. Not applicable could for example
mean that if a class doesn’t have an interface, the Interface Segregation Principle
does not apply to that class.
7.1 Summary
Legend:
35
OCP - V If we want to add a new option for the display value, this can a↵ect
many parts of our code. We might have to change the state construction
and/or logic inside calculate who controls the states as well as the props
for Display. To extend the class we therefore have to modify it, violating
this principle.
LSP - F To follow this principle the class has to be able to substitute the
functionality of its parent class React.Component. The parent class has
one method, called render, that needs to be implemented to substitute
its functionality. The class implements the render-method which means it
adheres to this principle.
DIP - F The class uses the calculate function which is defined elsewhere, mean-
ing that it’s dependent on the abstraction of calculate, not its concretion.
7.2.2 Display.js
SRP - F The class has one purpose, which is to build the user interface for the
display, and everything the class does is related to its purpose. The class
therefore follows this principle.
OCP - F If we want to extend the behavior of this class, we can easily do that
without changing any existing code. This means that the class is open for
extension meanwhile closed for modification, following this principle.
LSP - F Same argument as for App.js in Section 7.2.1 LSP.
7.2.3 ButtonPanel.js
SRP - F Same argument as for Display.js in Section 7.2.2 SRP.
OCP - F Same argument as for Display.js in Section 7.2.2 OCP.
LSP - F Same argument as for App.js in Section 7.2.1 LSP.
7.2.4 Button.js
SRP - F Same argument as for Display.js in Section 7.2.2 SRP.
OCP - F Same argument as for Display.js in Section 7.2.2 OCP.
LSP - F Same argument as for App.js in Section 7.2.1 LSP.
7.2.5 calculate.js
SRP - V This function has many responsibilities and therefore has many rea-
sons to change. It has to keep track of the di↵erent button inputs, perform
a few operations depending on the input (or lets operate perform some of
the operations), and update the state value in App. The division between
calculate and operate is not clear, meaning that their responsibilities
might interfere. This violates this principle.
36
OCP - V The function is hard to extend without having to modify a large
portion of it. If we want to add a new type of calculation, we need to
add a new if-statement inside the function and depending on what type of
calculation it is we might need to edit the function operate as well. This
violates this principle.
7.2.6 operate.js
SRP - V This function is responsible for many di↵erent types of operations
and therefore has many reasons to change. As mentioned in Section 7.2.5
SRP, the division of responsibilities between calculate and operate are
not clear. This violates this principle.
OCP - V Same argument as for calculate.js in Section 7.2.5 OCP.
7.2.7 isNumber.js
SRP - F The function serves one single purpose, to see if its input is a number
or not. This follows this principle.
37
8 Result - The rewritten application
8.1 Summary
In the rewritten application, displayed in Appendix B, we see many changes
made to better adhere to the SOLID principles. There are four major changes
made:
1. The state of the calculation, previously handled by App has been moved
as is now handled by the component called CalculatorContext. The
context provides a way to share values throughout the component tree
without having to pass around props between every level of the tree. In the
original application, App had to send the current state of the application
as a prop to its child components but it didn’t have any use for the state
itself, meaning it was dependent on something it didn’t use. By creating a
CalculatorContext the child components can use and update the current
state without being dependent on App.
2. The button has been broken down into three di↵erent buttons depending
on the type. The types have been defined by how the calculator handles
that type of button input, and the buttons with the same type of input
handling have been grouped. A common parent class has been created
for the buttons, this is to make sure that the buttons are implemented
correctly to what the application expects. This parent class acts as a type
of interface and has been created using the technique stated in Section
2.1.5.
3. Each type of button has been issued an input handler method, these han-
dlers look at the current state of the application and then determine what
to do with the current input. This is part of what the calculate function
used to do in the original application, but in the rewritten application the
input handlers don’t need to keep track of what button is pressed, they
only need to know if a button is pressed. This makes the input handlers
more extendable and ready for any kind of input as long as they adhere
to their specific type.
4. The di↵erent operations and mathematical calculations made by the calcu-
lator have been broken down into separate classes and grouped depending
on the type. The types have been broken down into conversions, numbers,
or operations. Independent of type, all classes share a common parent class
to be derived from that makes sure the classes are implemented correctly
to what the application expects. This parent class therefore acts as a type
of interface and has been created using the technique stated in Section
2.1.5. The functionality of these classes has previously been done both by
the calculate and operate functions and by breaking it down we create
more specific classes that act independently from each other, making the
code more extendable, maintainable, and clear.
38
Figure 9: The code structure of the rewritten application
39
JavaScript files that have been rewritten, they are the only ones showing an
increase in the number of files and lines.
Original application
Language Files Code Comments Blank Total lines
JavaScript 9 248 23 39 310
CSS 5 96 0 19 115
JSON 2 56 0 2 58
HTML 1 22 10 1 33
Markdown 1 13 0 14 27
Total 18 435 33 75 543
Rewritten application
Language Files Code Comments Blank Total lines
JavaScript 24 404 0 112 516
CSS 5 96 0 19 115
JSON 2 56 0 2 58
HTML 1 22 10 1 33
Markdown 1 13 0 14 27
Total 33 591 10 148 749
8.3.2 Display.js
This component has not changed significantly, the main change is that it now
gets its display value from CalculatorContext and not from App. The logic for
which value to display was previously handled by App, but it is now done here
in Display instead.
40
8.3.3 ButtonPanel.js
The ButtonPanel is still responsible for building the interface for the button
panel, but now it’s no longer responsible for triggering the click method in
its parent component. The buttons are more specialised than before so the
component now needs to keep track of which function that belongs to each
button and includes them as props to the respective button.
8.3.4 ButtonInterface.js
This component is new for the rewritten application and is the parent class
for all buttons used in the application. Its purpose is to ensure that the class
derived from this parent implement the required methods. In that way it works
as a type of interface, in this case making sure that our buttons behave the
way they should. Unlike the original version of the application, the buttons
themselves are now responsible for what happens when the buttons are clicked.
This action was previously done in App.
8.3.5 ConversionButton.js
This component is new for the rewritten application and is derived from the
ButtonInterface class. The component is a button meant to be used for con-
version operations. Conversions, in this case, is defined as an operation that
can be performed with only one number input, for example adding a decimal,
converting a number from a positive number to a negative number, or finding
the square root of a number.
8.3.6 NumberButton.js
This component is new for the rewritten application and is derived from the
ButtonInterface class. The component is a button meant to be used for num-
ber inputs.
8.3.7 OperationButton.js
This component is new for the rewritten application and is derived from the
ButtonInterface class. The component is a button meant to be used for oper-
ations. Operations in this case are defined as an action that takes two number
inputs and performs a mathematical calculation, for example, addition or sub-
traction.
8.3.8 CalculatorContext.js
This component is new for the rewritten application and creates a context for
the application which provides a way to share values throughout the compo-
nent tree without having to pass around props between every level of the tree.
The component keeps track of the current state of the application, which was
previously handled by App, and every time the state needs to be updated, the
41
method updateContext is called. Updating the state was previously handled
in calculate.
8.3.9 OperationInterface.js
This class is new for the rewritten application and is the parent class for all op-
erations and conversions we need in our calculator. Operations are actions that
require two inputs and conversions are actions that require one or no input. Its
purpose is to make sure our operations and conversions implement the required
method operation. Similar to the ButtonInterface class, it thereby works as
a type of interface, making sure our operations and conversions behave the way
we expect them to.
8.3.10 Add.js
This operation class is new for the rewritten application and is derived from the
OperationInterface. Its purpose is to add two numbers together and return
the result. This operation was previously a part of the function operation.
8.3.11 AddDecimal.js
This conversion class is new for the rewritten application and is derived from
the OperationInterface. Its purpose is to add a decimal to a number and
return the value. This action was previously done by the function calculate.
8.3.12 ClearDisplay.js
This conversion class is new for the rewritten application and is derived from
the OperationInterface. Its purpose is to return a null value which is used
to display of the calculator. This action was previously done by the function
calculate.
8.3.13 ConvertNumber.js
This conversion class is new for the rewritten application and is derived from the
OperationInterface. Its purpose is to convert a positive number to a negative
one or vice versa and return the value. This action was previously done by the
function calculate.
8.3.14 Divide.js
This operation class is new for the rewritten application and is derived from
the OperationInterface. Its purpose is to divide one number with another
and return the result. This operation was previously a part of the function
operation.
42
8.3.15 Equals.js
This conversion class is new for the rewritten application and is derived from
the OperationInterface. Its purpose is to perform the current operation that
has been entered into the application. This action was previously done by the
function calculate.
8.3.16 Multiply.js
This operation class is new for the rewritten application and is derived from the
OperationInterface. Its purpose is to multiply two numbers with each other
and then return the result. This operation was previously a part of the function
operation.
8.3.17 Squared.js
This conversion class is new for the rewritten application and is derived from the
OperationInterface. Its purpose is to calculate the square root of a number
and return the value.
8.3.18 Subtract.js
This operation class is new for the rewritten application and is derived from
the OperationInterface. Its purpose is to subtract one number from another
and return the result. This operation was previously a part of the function
operation.
8.3.19 UpdateValue.js
This class is new for the rewritten application and is derived from the class
OperationInterface. It either returns the current input number value or con-
cats the input number value to the current display value. This is used when
several number buttons are pressed after each other or if a number button is
pressed after an operation button. This action was previously done by the
function calculate.
8.3.20 handleConverionInput.js
This function is new for the rewritten application and handles inputs from
conversion buttons. The functions make sure that conversion inputs are handled
correctly depending on the current state of the application. This action was
previously done by the function calculate.
8.3.21 handleNumberInput.js
This function is new for the rewritten application and handles inputs from
number buttons. The functions make sure that number inputs are handled
43
correctly depending on the current state of the application. This action was
previously done by the function calculate.
8.3.22 handleOperationInput.js
This function is new for the rewritten application and handles inputs from
operation buttons. The functions make sure that operation inputs are handled
correctly depending on the current state of the application. This action was
previously done by the function calculate.
44
9 Result - SOLID analysis of the rewritten ap-
plication
This section analyses how well the rewritten application follows the SOLID
principles. Out of the three remaining entities from the original version, App,
Display and ButtonPanel, none of them display any violations of the SOLID
principles anymore. The functionality in the other previously existing entities
calculate, operate, button and isNumber has, as explained in Section 8.1, been
broken up and moved around in the rewritten application. This was necessary
in order to fix the violations against the SOLID principles that existed in these
entities before.
45
9.1 Summary
Legend:
46
9.2 Detailed analysis of each module
9.2.1 App.js
In the original application, this class violated the SRP and the OCP. It followed
the LSP and the DIP. Since the class is no longer dependent on any lower level
modules, the DIP is no longer applicable to this class.
SRP The class has one responsibility and therefore has only one reason to
change which is if a component is added or removed from the render
method.
OCP We can extend this class’s behavior without modifying any existing code.
For example add a new component.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
9.2.2 Display.js
In the original application, this class didn’t violate any of the principles. Just
like the original, it still follows the SRP, the OCP and LSP with the addition
of the DIP.
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the display value is changed.
OCP We can extend this class’s behavior without modifying any existing code.
For example add a new component.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
DIP The class is dependent on the CalculatorContext constant provided by
the CalculatorContextProvider class. The constant is an abstraction.
9.2.3 ButtonPanel.js
In the original application, this class didn’t violate any of the principles and
just like the original, it still follows the SRP, the OCP and LSP.
SRP The class has one responsibility and therefore has only one reason to
change which is if we want to add or remove a button from the button
panel.
OCP We can extend this class’s behavior without modifying any existing code.
For example add a new button.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
47
9.2.4 ButtonInterface.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the buttons changes.
OCP We can extend this class’s behavior without modifying any existing code.
For example create a new type of button extended from it.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
ISP The methods defined in the interface are related only to the buttons and
the buttons don’t need to implement any methods that they don’t need.
9.2.5 ConversionButton.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the conversion button changes.
OCP We can extends this class without modifying any existing code. For
example we can render di↵erent conversion buttons with di↵erent name
and function properties without ever changing the actual class.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
DIP The class is dependent on the CalculatorContext constant provided by
the CalculatorContextProvider class. The constant is an abstraction.
9.2.6 NumberButton.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the number button changes.
OCP We can extends this class without modifying any existing code. For exam-
ple we can render di↵erent number buttons with di↵erent name properties
without ever changing the actual class.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
DIP The class is dependent on the CalculatorContext constant provided by
the CalculatorContextProvider class. The constant is an abstraction.
48
9.2.7 OperationButton.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the operation button changes.
OCP We can extends this class without modifying any existing code. For
example we can render di↵erent operation buttons with di↵erent name
and function properties without ever changing the actual class.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
DIP The class is dependent on the CalculatorContext constant provided by
the CalculatorContextProvider class. The constant is an abstraction.
9.2.8 CalculatorContext.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the context changes.
OCP We can extend this class without modifying any existing code. The class
renders its children and not specific components, making it easy to extend
without modifying it.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
9.2.9 OperationInterface.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements for the operations changes.
OCP We can extend this class’s behavior without modifying any existing code.
For example create a new type of operation extended from it.
LSP The class uses the render-method which is the only criteria it has to follow
to substitute its parent class React.Component [19].
ISP The methods defined in the interface are related only to the operations
and the operations don’t need to implement any methods that they don’t
need.
49
9.2.10 Add.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.11 AddDecimal.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.12 ClearDisplay.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.13 ConvertNumber.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.14 Divide.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
50
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.15 Equals.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.16 Multiply.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.17 Squared.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.18 Subtract.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
51
9.2.19 UpdateValue.js
The class displays no violations of the SOLID principles. The class follows these
principles:
SRP The class has one responsibility and therefore has only one reason to
change which is if the requirements of the operation changes.
LSP The class implements the desired methods defined in its parent class which
is the only criteria it has to follow to be substitutable.
9.2.20 handleConversionInput.js
The function displays no violations of the SOLID principles. The function
follows these principles:
SRP The function has one responsibility and therefore has only one reason to
change which is if the requirements for the input handler changes.
DIP The function is dependent on the operation provided by the context vari-
able. It is also dependent on the operation provided by the input variable.
Both of these variables are abstractions.
9.2.21 handleNumberInput.js
The function displays no violations of the SOLID principles. The function
follows these principles:
SRP The function has one responsibility and therefore has only one reason to
change which is if the requirements for the input handler changes.
9.2.22 handleOperationInput.js
The function displays no violations of the SOLID principles. The function
follows these principles:
SRP The function has one responsibility and therefore has only one reason to
change which is if the requirements for the input handler changes.
DIP The function is dependent on the operation provided by the context vari-
able. This operation is an abstractions.
52
10 Result - Code Quality Metrics
The two application versions have been evaluated according to the Chidamber
and Kemerer metrics suite for object-oriented systems. The Low/High values
represent the classes with the lowest and highest metric in the set of classes in
each application version. Counting the built-in class Component from the React
library, the original application has 5 classes while the rewritten application has
20. We’ve decided to count this base class since it contributes to the inheritance
tree and therefore a↵ects these metrics.
10.1 Summary
The rewritten application displays mixed values compared to the original, most
of the values have improved but a few have become worse. On average however
we can see a positive trend for most of the metrics. The metric that has changed
the most in value is the Number Of Children (NOC) metric where the highest
value has increased from 4 to 10, however on average the value has only increased
by 0.1. The Lack of Cohesion in Methods (LCOM) metric is zero for both
applications for the simple reason that the application uses no instance variables,
which is one important variable for calculating the LCOM metric.
Table 5: Summary of the measured Chidamber and Kemerer metrics suite values
in the original application.
53
10.3 Metric values in the rewritten application
The rewritten application has 19 classes.
Table 6: Summary of the measured Chidamber and Kemerer metrics suite values
in the rewritten application.
54
11 Result - Expandability and maintenance eval-
uation
11.1 Summary
To analyse expandability and maintenance in the two versions of the applica-
tion, the method for calculating percentage was replaced with a method that
calculates a number to the power of two in the calculator. To do this, a button
had to be changed to match the new calculation and new logic for calculating
the power of two had do be added while the logic for calculating percentage
could be deleted. The programs can be found in Appendix C.
• In both versions of the application we had to edit the ButtonPanel class
to replace a button on the calculator to match the new equation method.
So far the two versions display the same level of complexity.
• In the original version of the application we had to edit the function
calculate to remove the old calculation for percentage and add the new
calculation for calculating the power of two, creating a new if statement
that handles the new calculation input. We also have to understand how
the application updates the current state of the application in App to re-
turn the correct data object. This means we had to modify our existing
code, requiring a higher level of understanding of the existing code to do
the modification without harming the application.
• In the rewritten version of the application we didn’t need to edit any more
existing code, all we had to do was delete the old class that contained
the calculation for percentage and add a new class for the power of two
calculation. No other classes or entities was a↵ected by these changes than
the classes in question.
To summarise, the original application shows a higher level of complexity
since you need a greater understanding of the existing code to do the changes.
In the rewritten application, you can focus on the changes and not worry about
what the rest of the application does, all changes you make and all you need to
know about the existing code is related to the task at hand.
11.2 In numbers
Table 7: Summary of the number of lines and files that had to be deleted,
edited or added in the two application version during the expandability and
maintenance evaluation.
55
56
12 Discussion
12.1 The rewritten application
The rewritten application has greatly changed in structure compared to the
original. Most of the changes have been done with an object-oriented design in
mind, since the SOLID principles are mainly mentioned in an object-oriented
context. You can argue that not all of these solutions are necessary for the
application to work, for example the self claimed ”class interfaces”. You could
create operations and buttons without these and they don’t a↵ect any of the
requirements of the actual application. However they do make some things a lot
easier. The purpose of these interfaces is to create a contract between the com-
ponents and other components using them, making sure they act as promised.
This helps greatly if you look at for example the LSP. The interfaces makes
sure that the components implements the methods defined in the interface, just
like the LSP makes sure that a subclass implements the methods defined in its
super-class. The goals of LSP are of course more than that, but it’s one of the
examples applicable in this context.
There are probably a thousand ways you could choose to rewrite this appli-
cation to adhere to the SOLID principles, so it’s important to remember that
this is just one of the possible solutions.
57
12.2.2 Open Closed Principle
This principle was one of the more difficult to adhere to, since it’s hard to know
when this principle is applicable and not. For example, looking at our subtyped
operation classes, they are pretty un-extendable without modification. However
you would probably never need to extend these classes, they are small and
serve one very simple purpose. It’s difficult to completely make our modules
closed since it’s impossible to know exactly every kind of change that might be
needed in our system for all future to come. As Robert C Martin describes, the
purpose is not to apply rampant abstraction to every part of a system, but to
apply abstraction to those parts who might require frequent change [11]. That’s
why it’s important to be strategic when it comes to this principle and try to
predict the most likely changes to close the modules too. For example in our
case we might not need to extend our specific operations anytime soon, addition
and subtraction have not changed that much in the last hundred years, however,
we might need to add more operations to our calculator in the future. That’s
why the OperationInterface is extendable instead of our specific operations.
58
12.2.5 Dependency Inversion Principle
This principle came quite naturally following the others. By separating func-
tionality into single responsibility entities and creating input handlers and in-
terfaces we have succeeded in simplifying and abstracting the way our entities
communicate and depend on each other.
You might argue that the input handlers are quite dependent on the details of
the state in CalculatorContext and that that might violate the DIP. However
looking at the purpose of these handlers, they are very related to the state
structure since they need to analyse the current state of the application and
decide what the next state should be. Also, the input handles are only dependent
on the state structure and not how CalculatorContext handles the state when
they return it to the CalculatorContext.
59
top of the class hierarchy as well which is not desirable. Since the WMC value
for the rewritten application is lower and the NOC value is higher than the
original, this could indicate that the rewritten application has lesser complexity
at the top of the class hierarchy than the original application.
60
In the original application, we have to understand the entire calculate func-
tion including how the application handles inputs and how the application up-
dates the current state of the application in App in order to add our new feature.
These things are unrelated to the new feature we are trying to add and we should
not have to worry about them.
The numbers do not really tell us anything significant. The changes result in
about the same amount of line edits which is quite small, the biggest di↵erence
is the deleted/added files vs edited files which is related to the earlier discussion.
61
13 Conclusion
This analysis was intended to answer four main questions:
1. How well does the rewritten application follow the SOLID principles com-
pared to the original?
The rewritten application does not violate any of the SOLID principles and also
follows more of the principles than the original application does. This is an
improvement from the original application. The analysis of the original appli-
cation can be found in section 7 and the analysis of the rewritten application
can be found in section 9.
2. Are any of the SOLID principles simply not possible to follow when using
React?
3. In terms of the goals of the SOLID principles, is the code better in the
rewritten application now that it follows them?
Looking at the main goals of the SOLID principles, which are to create flexible
code that is easy to maintain and scale, the rewritten application is better
than the original application. The results of the expandability and maintenance
evaluation of the original and rewritten application can be found in section 11
and 12.4.
4. Are there any measurable values that can indicate improvement now that
the application follows the SOLID principles?
By using the Chidamber and Kemerer metrics suite we have shown that there
are measurable values to analyse the quality of our code. A majority of the
metrics shows an improving trend which indicates that the code is better in the
rewritten application. The metric values can be found in section 10 and 12.3.
Looking at these questions we can conclude that the code in the rewritten
application is better than in the original application. We can draw this conclu-
sion from mainly two point of views. The first is that the rewritten application
is easier to maintain and scale. The second is the measurable values that show
an improvement of quality in the rewritten application. By incorporating the
SOLID principles, we have therefore challenged the React way of thinking and
shown that an object-oriented approach is possible and can even be beneficial
when developing a React application.
62
14 Future Work
This project looks at the React code from a very object-oriented SOLID point
of view, however it does not take into account how the code analysis would look
from a React point of view. By incorporating the thoughts of the React creators
and what they consider ”good React code”, we might discover a more extended
analysis than could fit in the time frame of this report.
Another interesting aspect would be to analyse the code from a more sub-
jective point of view. This report strives to not make a subjective analysis in
terms of, for example, what good code should look like. It can be difficult to
find consistent sources on this matter, so a good approach would likely be to
incorporate many sources and try to find unity between them.
63
References
[1] Deborah J Armstrong. “The quarks of object-oriented development”. In:
Communications of the ACM 49.2 (2006), pp. 124–128. url: https://
dl.acm.org/doi/10.1145/1113034.1113040.
[2] Grady Booch, James Rumbaugh, and Ivar Jacobson. The Unified Modeling
Language User Guide. English. 2nd,Revis. Addison Wesley Professional,
2005. isbn: 0321267974.
[3] Grady Booch et al. Object-Oriented Analysis and Design with Applica-
tions, Third Edition. Third. Addison-Wesley Professional, 2007. isbn: 9780201895513.
[4] Andrea Chiarelli. Mastering JavaScript Object-Oriented Programming. Packt
Publishing, 2016. isbn: 9781785889103. url: https://learning.oreilly.
com/library/view/mastering-javascript-object-oriented/9781785889103/
?ar.
[5] Shyam R Chidamber and Chris F Kemerer. “A metrics suite for object ori-
ented design”. In: IEEE Transactions on software engineering 20.6 (1994),
pp. 476–493.
[6] David Flanagan. JavaScript: The Definitive Guide, 7th Edition. 7th ed.
O’Reilly Media, Inc, 2020. isbn: 9781491952016.
[7] Andrew Hunt, David Thomas, and Ward Cunningham. The Pragmatic
Programmer: From Journeyman to Master. English. Boston: Addison Wes-
ley Professional, 1999. isbn: 9780201616224;020161622X;
[8] Paul C Jorgensen. Software testing: a craftsman’s approach. CRC press,
2018.
[9] Barbara Liskov. “Keynote address - data abstraction and hierarchy”. En-
glish. In: SIGPLAN notices 23.5 (1988), pp. 17–34.
[10] Robert C Martin. Clean code: a handbook of agile software craftsmanship.
Pearson Education, 2009.
[11] Robert C. Martin. Agile software development: principles, patterns, and
practices. 1st ed. Pearson, 2014.
[12] T. J. McCabe. “A Complexity Measure”. In: IEEE Transactions on Soft-
ware Engineering SE-2.4 (1976), pp. 308–320. doi: 10.1109/TSE.1976.
233837.
[13] Steve McConnell. Code complete. English. Second. Redmond, Washington:
Microsoft Press, 2004. isbn: 0735635072;0735619670;9780735635074;9780735619678;
[14] Bertrand Meyer. Object-oriented software construction. Vol. 2. Prentice
hall Englewood Cli↵s, 1997.
[15] Matthias Noback. Principles of package design: creating reusable software
components. 1st ed. Apress, 2018.
[16] Oracle.com Java Documentation. url: https : / / docs . oracle . com /
javase / tutorial / java / nutsandbolts / variables . html (visited on
10/12/2020).
64
[17] Matthew J Parkinson and Gavin M Bierman. “Separation logic, abstrac-
tion and inheritance”. In: ACM SIGPLAN Notices 43.1 (2008), pp. 75–
86.
[18] Jack Purdum. Beginning C# 3.0. Wrox, 2008. isbn: 9780470261293. url:
https : / / learning . oreilly . com / library / view / beginning - c - 30 /
9780470261293/?ar.
[19] React homepage. url: https://reactjs.org/ (visited on 06/21/2020).
[20] Carlos S. Roldán. React Cookbook: Create Dynamic Web Apps with Re-
act Using Redux, Webpack, Node. js, and GraphQL. Packt Publishing,
Limited, 2018. isbn: 9781783980727.
[21] Vaskaran Sarcar. Interactive Object Oriented Programming in Java: Learn
and Test Your Programming Skills. Apress, 2019.
[22] Stack Overflow Developer Survey 2020. url: https://insights.stackoverflow.
com/survey/2020 (visited on 12/14/2020).
[23] Umesh Tiwari and Santosh Kumar. “Cyclomatic complexity metric for
component based software”. In: ACM SIGSOFT Software Engineering
Notes 39.1 (2014), pp. 1–6.
[24] Nenad Ukić, Josip Maras, and Ljiljana Šerić. “The influence of cyclomatic
complexity distribution on the understandability of xtUML models”. En-
glish. In: Software quality journal 26.2 (2018), pp. 273–319.
[25] W3schools.com React Documentation. url: https : / / www . w3schools .
com/react/react_state.asp (visited on 10/12/2020).
65
A Appendix - The original application
A.1 App.js
1 import React from " react " ;
2 import Display from " ./ Display " ;
3 import ButtonPanel from " ./ ButtonPanel " ;
4 import calculate from " ../ logic / calculate " ;
5 import " ./ App . css " ;
6
7 export default class App extends React . Component {
8 state = {
9 total : null ,
10 next : null ,
11 operation : null ,
12 };
13
14 handleClick = buttonName = > {
15 this . setState ( calculate ( this . state , buttonName ) ) ;
16 };
17
18 render () {
19 return (
20 < div className = " component - app " >
21 < Display value ={ this . state . next || this . state . total || " 0 " }
/>
22 < ButtonPanel clickHandler ={ this . handleClick } / >
23 </ div >
24 );
25 }
26 }
A.2 Display.js
1 import React from " react " ;
2 import PropTypes from " prop - types " ;
3
4 import " ./ Display . css " ;
5
6 export default class Display extends React . Component {
7 static propTypes = {
8 value : PropTypes . string ,
9 };
10
11 render () {
12 return (
13 < div className = " component - display " >
14 <div >{ this . props . value } </ div >
15 </ div >
16 );
17 }
18 }
A.3 ButtonPanel.js
1 import Button from " ./ Button " ;
66
2 import React from " react " ;
3 import PropTypes from " prop - types " ;
4
5 import " ./ ButtonPanel . css " ;
6
7 export default class ButtonPanel extends React . Component {
8 static propTypes = {
9 clickHandler : PropTypes . func ,
10 };
11
12 handleClick = buttonName = > {
13 this . props . clickHandler ( buttonName ) ;
14 };
15
16 render () {
17 return (
18 < div className = " component - button - panel " >
19 <div >
20 < Button name = " AC " clickHandler ={ this . handleClick } / >
21 < Button name = " +/ - " clickHandler ={ this . handleClick } / >
22 < Button name = " % " clickHandler ={ this . handleClick } / >
23 < Button name = " " clickHandler ={ this . handleClick } orange
/>
24 </ div >
25 <div >
26 < Button name = " 7 " clickHandler ={ this . handleClick } / >
27 < Button name = " 8 " clickHandler ={ this . handleClick } / >
28 < Button name = " 9 " clickHandler ={ this . handleClick } / >
29 < Button name = " x " clickHandler ={ this . handleClick } orange
/>
30 </ div >
31 <div >
32 < Button name = " 4 " clickHandler ={ this . handleClick } / >
33 < Button name = " 5 " clickHandler ={ this . handleClick } / >
34 < Button name = " 6 " clickHandler ={ this . handleClick } / >
35 < Button name = " -" clickHandler ={ this . handleClick } orange
/>
36 </ div >
37 <div >
38 < Button name = " 1 " clickHandler ={ this . handleClick } / >
39 < Button name = " 2 " clickHandler ={ this . handleClick } / >
40 < Button name = " 3 " clickHandler ={ this . handleClick } / >
41 < Button name = " + " clickHandler ={ this . handleClick } orange
/>
42 </ div >
43 <div >
44 < Button name = " 0 " clickHandler ={ this . handleClick } wide / >
45 < Button name = " . " clickHandler ={ this . handleClick } / >
46 < Button name = " = " clickHandler ={ this . handleClick } orange
/>
47 </ div >
48 </ div >
49 );
50 }
51 }
A.4 Button.js
67
1 import React from " react " ;
2 import PropTypes from " prop - types " ;
3 import " ./ Button . css " ;
4
5 export default class Button extends React . Component {
6 static propTypes = {
7 name : PropTypes . string ,
8 orange : PropTypes . bool ,
9 wide : PropTypes . bool ,
10 clickHandler : PropTypes . func ,
11 };
12
13 handleClick = () = > {
14 this . props . clickHandler ( this . props . name ) ;
15 };
16
17 render () {
18 const className = [
19 " component - button " ,
20 this . props . orange ? " orange " : " " ,
21 this . props . wide ? " wide " : " " ,
22 ];
23
24 return (
25 < div className ={ className . join ( " " ) . trim () } >
26 < button onClick ={ this . handleClick } >{ this . props . name } </
button >
27 </ div >
28 );
29 }
30 }
A.5 calculate.js
1 import Big from " big . js " ;
2
3 import operate from " ./ operate " ;
4 import isNumber from " ./ isNumber " ;
5
6 /* *
7 * Given a button name and a calculator data object , return an
updated
8 * calculator data object .
9 *
10 * Calculator data object contains :
11 * total : String the running total
12 * next : String the next number to be operated on with the
total
13 * operation : String + , -, etc .
14 */
15 export default function calculate ( obj , buttonName ) {
16 if ( buttonName === " AC " ) {
17 return {
18 total : null ,
19 next : null ,
20 operation : null ,
21 };
68
22 }
23
24 if ( isNumber ( buttonName ) ) {
25 if ( buttonName === " 0 " && obj . next === " 0 " ) {
26 return {};
27 }
28 // If there is an operation , update next
29 if ( obj . operation ) {
30 if ( obj . next ) {
31 return { next : obj . next + buttonName };
32 }
33 return { next : buttonName };
34 }
35 // If there is no operation , update next and clear the value
36 if ( obj . next ) {
37 const next = obj . next === " 0 " ? buttonName : obj . next +
buttonName ;
38 return {
39 next ,
40 total : null ,
41 };
42 }
43 return {
44 next : buttonName ,
45 total : null ,
46 };
47 }
48
49 if ( buttonName === " % " ) {
50 if ( obj . operation && obj . next ) {
51 const result = operate ( obj . total , obj . next , obj . operation ) ;
52 return {
53 total : Big ( result )
54 . div ( Big ( " 100 " ) )
55 . toString () ,
56 next : null ,
57 operation : null ,
58 };
59 }
60 if ( obj . next ) {
61 return {
62 next : Big ( obj . next )
63 . div ( Big ( " 100 " ) )
64 . toString () ,
65 };
66 }
67 return {};
68 }
69
70 if ( buttonName === " . " ) {
71 if ( obj . next ) {
72 // ignore a . if the next number already has one
73 if ( obj . next . includes ( " . " ) ) {
74 return {};
75 }
76 return { next : obj . next + " . " };
77 }
69
78 return { next : " 0. " };
79 }
80
81 if ( buttonName === " = " ) {
82 if ( obj . next && obj . operation ) {
83 return {
84 total : operate ( obj . total , obj . next , obj . operation ) ,
85 next : null ,
86 operation : null ,
87 };
88 } else {
89 // ’= ’ with no operation , nothing to do
90 return {};
91 }
92 }
93
94 if ( buttonName === " +/ - " ) {
95 if ( obj . next ) {
96 return { next : ( -1 * parseFloat ( obj . next ) ) . toString () };
97 }
98 if ( obj . total ) {
99 return { total : ( -1 * parseFloat ( obj . total ) ) . toString () };
100 }
101 return {};
102 }
103
104 // Button must be an operation
105
106 // When the user presses an operation button without having
entered
107 // a number first , do nothing .
108 // if (! obj . next && ! obj . total ) {
109 // return {};
110 // }
111
112 // User pressed an operation button and there is an existing
operation
113 if ( obj . operation ) {
114 return {
115 total : operate ( obj . total , obj . next , obj . operation ) ,
116 next : null ,
117 operation : buttonName ,
118 };
119 }
120
121 // no operation yet , but the user typed one
122
123 // The user hasn ’t typed a number yet , just save the operation
124 if (! obj . next ) {
125 return { operation : buttonName };
126 }
127
128 // save the operation and shift ’ next ’ into ’ total ’
129 return {
130 total : obj . next ,
131 next : null ,
132 operation : buttonName ,
70
133 };
134 }
A.6 operate.js
1 import Big from " big . js " ;
2
3 export default function operate ( numberOne , numberTwo , operation ) {
4 const one = Big ( numberOne || " 0 " ) ;
5 const two = Big ( numberTwo || ( operation === " " || operation ===
’x ’ ? " 1 " : " 0 " ) ) ; // If dividing or multiplying , then 1
maintains current value in cases of null
6 if ( operation === " + " ) {
7 return one . plus ( two ) . toString () ;
8 }
9 if ( operation === " -" ) {
10 return one . minus ( two ) . toString () ;
11 }
12 if ( operation === " x " ) {
13 return one . times ( two ) . toString () ;
14 }
15 if ( operation === " " ) {
16 if ( two === " 0 " ) {
17 alert ( " Divide by 0 error " ) ;
18 return " 0 " ;
19 } else {
20 return one . div ( two ) . toString () ;
21 }
22 }
23 throw Error ( ‘ Unknown operation ’$ { operation } ’ ‘) ;
24 }
A.7 isNumber.js
1 export default function isNumber ( item ) {
2 return /[0 -9]+/. test ( item ) ;
3 }
71
14 < ButtonPanel / >
15 </ CalculatorContextProvider >
16 </ div >
17 );
18 }
19 }
20
21 export default App ;
B.2 Display.js
1 import React from " react " ;
2 import { C a l c u l a t o r C o n t e x t } from " ../ context / C a l c u l a t o r C o n t e x t " ;
3
4 import " ./ Display . css " ;
5
6 class Display extends React . Component {
7 static contextType = C a l c u l a t o r C o n t e x t ;
8
9 render () {
10 const { x , y } = this . context ;
11
12 return (
13 < div className = " component - display " >
14 <div >{ y || x || " 0 " } </ div >
15 </ div >
16 );
17 }
18 }
19
20 export default Display ;
B.3 ButtonPanel.js
1 import React from " react " ;
2 import " ./ ButtonPanel . css " ;
3 import add from " ../ logic / operations / Add " ;
4 import clearDisplay from " ../ logic / operations / ClearDisplay " ;
5 import convertNumber from " ../ logic / operations / ConvertNumber " ;
6 import addDecimal from " ../ logic / operations / AddDecimal " ;
7 import divide from " ../ logic / operations / Divide " ;
8 import equals from " ../ logic / operations / Equals " ;
9 import multiply from " ../ logic / operations / Multiply " ;
10 import squared from " ../ logic / operations / Squared " ;
11 import subtract from " ../ logic / operations / Subtract " ;
12 import NumberButton from ’ ./ buttons / NumberButton ’;
13 import C o n v e r s io n B u t t o n from ’ ./ buttons / C o n v e r s i o n B u t t o n ’;
14 import O p er at io n Bu tt on from ’ ./ buttons / Op e ra ti on B ut to n ’;
15
16 class ButtonPanel extends React . Component {
17
18 render () {
19 return (
20 < div className = " component - button - panel " >
21 <div >
22 < C o n v e r s i o n B u t t o n name = " AC " func ={ clearDisplay } / >
23 < C o n v e r s i o n B u t t o n name = " +/ - " func ={ convertNumber } / >
72
24 < C o n v e r s i o n B u t t o n name = " x & sup2 ; " func ={ squared } / >
25 < O pe ra ti o nB ut to n name = " " func ={ divide } orange / >
26 </ div >
27 <div >
28 < NumberButton name = " 7 " / >
29 < NumberButton name = " 8 " / >
30 < NumberButton name = " 9 " / >
31 < O pe ra ti o nB ut to n name = " x " func ={ multiply } orange / >
32 </ div >
33 <div >
34 < NumberButton name = " 4 " / >
35 < NumberButton name = " 5 " / >
36 < NumberButton name = " 6 " / >
37 < O pe ra ti o nB ut to n name = " -" func ={ subtract } orange / >
38 </ div >
39 <div >
40 < NumberButton name = " 1 " / >
41 < NumberButton name = " 2 " / >
42 < NumberButton name = " 3 " / >
43 < O pe ra ti o nB ut to n name = " + " func ={ add } orange / >
44 </ div >
45 <div >
46 < NumberButton name = " 0 " wide / >
47 < C o n v e r s i o n B u t t o n name = " . " func ={ addDecimal } / >
48 < C o n v e r s i o n B u t t o n name = " = " func ={ equals } orange / >
49 </ div >
50 </ div >
51 );
52 }
53 }
54
55 export default ButtonPanel ;
B.4 ButtonInterface.js
1 import React from " react " ;
2 import " ./ Button . css " ;
3
4 class Bu tt o nI nt er f ac e extends React . Component {
5
6 constructor ( props ) {
7 super ( props ) ;
8 this . className = ’ component - button ’;
9 }
10
11 h a n d l e B u t t o n I n p u t = () = > {
12 throw new TypeError ( this . constructor . name + ’ must implement
the h a n d l e B u t t o n I n p u t method ! ’)
13 }
14
15 render () {
16 throw new TypeError ( this . constructor . name + ’ must implement
the render method ! ’)
17 return (
18 <>
19 </ >
20 );
73
21 }
22 }
23
24 export default B u tt on In t er fa ce ;
B.5 ConversionButton.js
1 import React from ’ react ’;
2 import " ./ Button . css " ;
3 import B u tt on In t er fa ce from " ./ B ut to n In te rf a ce " ;
4 import h a n d l e C o n v e r s i o n I n p u t from " ../../ logic /
handleConversionInput ";
5 import { C a l c u l a t o r C o n t e x t } from " ../../ context / C a l c u l a t o r C o n t e x t "
;
6
7 class C o n v e r s i o n B u t t o n extends Bu tt o nI nt er f ac e {
8
9 static contextType = C a l c u l a t o r C o n t e x t ;
10
11 constructor ( props ) {
12 super ( props ) ;
13 this . className = [ this . className ,
14 ( props . wide ? " wide " : " " ) ,
15 ( props . orange ? " orange " : " " )
16 ]. join ( " " ) . trim () ;
17 this . func = props . func ;
18 this . name = props . name
19 }
20
21 h a n d l e B u t t o n I n p u t = () = > {
22 var newContext = h a n d l e C o n v e r s i o n I n p u t ( this . context , this .
func ) ;
23 this . context . updateContext ( newContext ) ;
24 }
25
26 render () {
27 return (
28 < div className ={ this . className } >
29 < button onClick ={ this . h a n d l e B u t t o n I n p u t } >{ this . name
} </ button >
30 </ div >
31 )
32 }
33 }
34
35 export default C o n v e r s io n B u t t o n ;
B.6 NumberButton.js
1 import React from ’ react ’;
2 import " ./ Button . css " ;
3 import B u tt on In t er fa ce from " ./ B ut to n In te rf a ce " ;
4 import h a n d l e N u m b e r I n p u t from " ../../ logic / h a n d l e N u m b e r I n p u t " ;
5 import { C a l c u l a t o r C o n t e x t } from " ../../ context / C a l c u l a t o r C o n t e x t "
;
6
7 class NumberButton extends Bu tt o nI nt er f ac e {
74
8
9 static contextType = C a l c u l a t o r C o n t e x t ;
10
11 constructor ( props ) {
12 super ( props ) ;
13 this . className = [ this . className ,
14 ( props . wide ? " wide " : " " )
15 ]. join ( " " ) . trim () ;
16 this . number = props . name ;
17 }
18
19
20 h a n d l e B u t t o n I n p u t = () = > {
21 var newContext = h a n d l e N u m b e r I n p u t ( this . context , this .
number ) ;
22 this . context . updateContext ( newContext ) ;
23 }
24
25 render () {
26 return (
27 < div className ={ this . className } >
28 < button onClick ={ this . h a n d l e B u t t o n I n p u t } >{ this .
number } </ button >
29 </ div >
30 )
31 }
32 }
33
34 export default NumberButton ;
B.7 OperationButton.js
1 import React from ’ react ’;
2 import " ./ Button . css " ;
3 import B u tt on In t er fa ce from " ./ B ut to n In te rf a ce " ;
4 import h a n d l e O p e r a t i o n I n p u t from " ../../ logic / h a n d l e O p e r a t i o n I n p u t "
;
5 import { C a l c u l a t o r C o n t e x t } from " ../../ context / C a l c u l a t o r C o n t e x t "
;
6
7 class Op er a ti on Bu t to n extends B u tt on In t er fa ce {
8
9 static contextType = C a l c u l a t o r C o n t e x t ;
10
11 constructor ( props ) {
12 super ( props ) ;
13 this . className = [ this . className ,
14 ( props . wide ? " wide " : " " ) ,
15 ( props . orange ? " orange " : " " )
16 ]. join ( " " ) . trim () ;
17 this . func = props . func ;
18 this . name = props . name ;
19 }
20
21 h a n d l e B u t t o n I n p u t = () = > {
22 var newContext = h a n d l e O p e r a t i o n I n p u t ( this . context , this .
func ) ;
75
23 this . context . updateContext ( newContext ) ;
24 }
25
26 render () {
27 return (
28 < div className ={ this . className } >
29 < button onClick ={ this . h a n d l e B u t t o n I n p u t } >{ this . name
} </ button >
30 </ div >
31 )
32 }
33 }
34
35 export default O p er at io n Bu tt on ;
B.8 CalculatorContext.js
1 import React , { createContext , Component } from ’ react ’;
2
3 export const C a l c u l a t o r C o n t e x t = createContext () ;
4
5 class C a l c u l a t o r C o n t e x t P r o v i d e r extends Component {
6 state = {
7 x : null ,
8 y : null ,
9 operation : null ,
10 }
11
12 updateContext = ( newContext ) = > {
13 if ( newContext === null ) {
14 return null ;
15 } else {
16 this . setState ({
17 x : newContext .x ,
18 y : newContext .y ,
19 operation : newContext . operation ,
20 })
21 }
22 }
23
24 render () {
25 return (
26 < C a l c u l a t o r C o n t e x t . Provider value ={{
27 ... this . state ,
28 updateContext : this . updateContext ,
29 }} >
30 { this . props . children }
31 </ C a l c u l a t o r C o n t e x t . Provider >
32 );
33 }
34 }
35
36 export default C a l c u l a t o r C o n t e x t P r o v i d e r ;
B.9 OperationInterface.js
1
76
2 class O p e r a t i o n I n t e r f a c e {
3
4 operation = () = > {
5 throw new TypeError (
6 this . constructor . name + ’ must implement the operation
method ! ’
7 );
8 }
9 }
10
11 export default O p e r a t i o n I n t e r f a c e ;
B.10 Add.js
1 import O p e r a t i o n I n t e r f a c e from ’ ./ O p e r a t i o n I n t e r f a c e ’;
2
3 class Add extends O p e r a t i o n I n t e r f a c e {
4
5 operation = (x , y ) = > {
6 var result = + x + + y ;
7 return result . toString () ;
8 }
9 }
10
11 let add = new Add () . operation ;
12
13 export default add ;
B.11 AddDecimal.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class AddDecimal extends O p e r a t i o n I n t e r f a c e {
4
5 operation = ( z ) = > {
6 if ( z . includes ( " . " ) ) {
7 return z ;
8 } else {
9 return z + " . " ;
10 }
11 }
12 }
13
14 let addDecimal = new AddDecimal () . operation ;
15
16 export default addDecimal ;
B.12 ClearDisplay.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class ClearDisplay extends O p e r a t i o n I n t e r f a c e {
4
5 operation = () = > {
6 return null ;
7 }
8 }
77
9
10 let clearDisplay = new ClearDisplay () . operation ;
11
12 export default clearDisplay ;
B.13 ConvertNumber.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class ConvertNumber extends O p e r a t i o n I n t e r f a c e {
4
5 operation = ( z ) = > {
6 return ( -1 * parseFloat ( z ) ) . toString () ;
7 }
8 }
9
10 let convertNumber = new ConvertNumber () . operation ;
11
12 export default convertNumber ;
B.14 Divide.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class Divide extends O p e r a t i o n I n t e r f a c e {
4
5 operation = (x , y ) = > {
6 if ( y === " 0 " ) {
7 return ( " err " )
8 } else {
9 var result = + x / + y ;
10 return result . toString () ;
11 }
12 }
13 }
14
15 let divide = new Divide () . operation ;
16
17 export default divide ;
B.15 Equals.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class Equals extends O p e r a t i o n I n t e r f a c e {
4
5 operation = (x , y , operation ) = > {
6 if (! operation ) {
7 return x ;
8 } else {
9 var result = operation (x , y ) ;
10 return result . toString () ;
11 }
12 }
13 }
14
15 let equals = new Equals () . operation ;
78
16
17 export default equals ;
B.16 Multiply.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class Multiply extends O p e r a t i o n I n t e r f a c e {
4
5 operation = (x , y ) = > {
6 var result = + x * + y ;
7 return result . toString () ;
8 }
9 }
10
11 let multiply = new Multiply () . operation ;
12
13 export default multiply ;
B.17 Squared.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class Squared extends O p e r a t i o n I n t e r f a c e {
4
5 operation = ( z ) = > {
6 var result = z * z ;
7 return result . toString () ;
8 }
9 }
10
11 let squared = new Squared () . operation ;
12
13 export default squared ;
B.18 Subtract.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class Subtract extends O p e r a t i o n I n t e r f a c e {
4
5 operation = (x , y ) = > {
6 var result = + x - + y ;
7 return result . toString () ;
8 }
9 }
10
11 let subtract = new Subtract () . operation ;
12
13 export default subtract ;
B.19 UpdateValue.js
1 import O p e r a t i o n I n t e r f a c e from " ./ O p e r a t i o n I n t e r f a c e " ;
2
3 class UpdateValue extends O p e r a t i o n I n t e r f a c e {
79
4
5 operation = (z , button ) = > {
6 if ( z === null ) {
7 return button ;
8 } else {
9 return z + button ;
10 }
11 }
12 }
13
14 let updateValue = new UpdateValue () . operation ;
15
16 export default updateValue ;
B.20 handleConversionInput.js
1
2 function h a n d l e C o n v e r s i o n I n p u t ( context , input ) {
3 var conversion ;
4 var newContext ;
5
6 if (! context . x ) {
7 return null ;
8 } else if ( context . operation && context . y ) {
9 var result = context . operation ( context .x , context . y ) ;
10 conversion = {
11 x : input ( result ) ,
12 y : null ,
13 operation : null ,
14 };
15 newContext = Object . assign ( context , conversion ) ;
16 return newContext ;
17 } else {
18 conversion = {
19 x : input ( context .x , context .y , context . operation ) ,
20 y : null ,
21 operation : null ,
22 };
23 newContext = Object . assign ( context , conversion ) ;
24 return newContext ;
25 }
26 }
27
28 export default h a n d l e C o n v e r s i o n I n p u t ;
B.21 handleNumberInput.js
1 import updateValue from " ./ operations / UpdateValue " ;
2
3 function h a n d l e N u m b e r I n p u t ( context , number ) {
4 var newContext ;
5
6 if (! context . operation ) {
7 var xUpdate = { x : updateValue ( context .x , number ) };
8 newContext = Object . assign ( context , xUpdate ) ;
9 return newContext ;
10 } else {
80
11 var yUpdate = { y : updateValue ( context .y , number ) };
12 newContext = Object . assign ( context , yUpdate ) ;
13 return newContext ;
14 }
15 }
16
17 export default h a n d l e N u m b e r I n p u t ;
B.22 handleOperationInput.js
1
2 function h a n d l e O p e r a t i o n I n p u t ( context , input ) {
3 var newContext ;
4
5 if (! context . x ) {
6 return null ;
7 } else if ( context . operation && context . y ) {
8 var result = {
9 x : context . operation ( context .x , context . y ) ,
10 y : null ,
11 operation : input ,
12 input : null ,
13 };
14 newContext = Object . assign ( context , result ) ;
15 return newContext ;
16 } else {
17 var o pe ra t io nU pd a te = {
18 operation : input ,
19 };
20 newContext = Object . assign ( context , op er at i on Up da t e ) ;
21 return newContext ;
22 }
23 }
24
25 export default h a n d l e O p e r a t i o n I n p u t ;
81
15 . div ( Big ( " 100 " ) )
16 . toString () ,
17 next : null ,
18 operation : null ,
19 };
20 }
21 if ( obj . next ) {
22 return {
23 next : Big ( obj . next )
24 . div ( Big ( " 100 " ) )
25 . toString () ,
26 };
27 }
28 return {};
29 }
30 ...
31
32 // After
33
34 // ButtonPanel . js - Edited
35 ...
36 < Button name = " x & sup2 ; " clickHandler ={ this . handleClick } / >
37 ...
38
39 // calculate . js - Edited
40
41 ...
42 if ( buttonName === " x " ) {
43 if ( obj . operation && obj . next ) {
44 const result = operate ( obj . total , obj . next , obj . operation ) ;
45 return {
46 total : Big ( result )
47 . times ( Big ( result ) )
48 . toString () ,
49 next : null ,
50 operation : null ,
51 };
52 }
53 if ( obj . next ) {
54 return {
55 next : Big ( obj . next )
56 . times ( Big ( obj . next ) )
57 . toString () ,
58 };
59 }
60 return {};
61 }
62 ...
82
7
8 // Percent . js
9 ...
10 class Percent extends O p e r a t i o n I n t e r f a c e {
11
12 operation = ( z ) = > {
13 var result = + z / 100;
14 return result . toString () ;
15 }
16 }
17
18 let percent = new Percent () . operation ;
19 ...
20
21 // After
22
23 // ButtonPanel . js - Edited
24 ...
25 < C o n v e r s i o n B u t t o n name = " x & sup2 ; " func ={ squared } / >
26 ...
27
28 // Squared . js - Added
29 ...
30 class Squared extends O p e r a t i o n I n t e r f a c e {
31
32 operation = ( z ) = > {
33 var result = z * z ;
34 return result . toString () ;
35 }
36 }
37
38 let squared = new Squared () . operation ;
39 ...
40
41 // Percent . js - Deleted
83