According to the US Department of Defense, “Well structured software is delivered in half the time, at half the cost, and with 8x less bugs”. In this tech talk on structural quality, we briefly discuss why software structure matters and how good structural quality results in better developer productivity, provides developer guidance, helps improved estimates, reduces risk & meeting delivery dates, focused testing, enhanced reuse & extensibility and helps divide the labour in large teams.
As Martin Fowler observed, “High internal quality reduces the cost of future features, meaning that putting the time into writing good code actually reduces cost.” This talk is entirely focused on improving the internal software and structural quality. In specific, we discuss code refactoring with specific examples and discuss restructuring with demonstrations. By attending this talk, you will have a good understanding on why structure matters and specific & effective ways to improve internal software quality.
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Refactoring & Restructuring - Improving the Code and Structure of Software
1. HOW TO IMPROVE SOFTWARE CODE &
STRUCTURE?
GANESH SAMARTHYAM (KONFHUB)
ganesh@konfhub.com
2. “HIGH INTERNAL QUALITY REDUCES THE
COST OF FUTURE FEATURES, MEANING THAT
PUTTING THE TIME INTO WRITING GOOD
CODE ACTUALLY REDUCES COST”
Martin Fowler
WHY CARE?
https://martinfowler.com/articles/is-quality-worth-cost.html
5. IMPROVING CODE
CODE SMELLS -> REFACTORING
public class PollutantEntry {
private Location location;
private LocalDateTime lastUpdate;
private PollutionData pollutionData;
/* The Builder class corresponds to the PollutantEntry - it helps create a Pollutant entry
object given the location object, time of reading, and the actual pollution reading
*/
public static class Builder {
PollutantEntry pollutantEntry = new PollutantEntry();
public Builder() {
}
public Builder location(Location location) {
pollutantEntry.location = location;
return this;
}
public Builder lastUpdate(LocalDateTime lastUpdate) {
pollutantEntry.lastUpdate = lastUpdate;
return this;
}
public Builder pollutionData(PollutionData pollutionData) {
pollutantEntry.pollutionData = pollutionData;
return this;
}
public PollutantEntry build() {
return pollutantEntry;
}
}
}
INTRODUCE ABSTRACTIONS &
USE BUILDER PATTERN!
8. EFFECTIVE APPROACH
CODE SMELLS -> REFACTORING
class Interpret {
private static Stack<Integer> executionStack = new Stack<>();
public static int interpret(byte[] byteCodes) {
int pc = 0;
while(pc < byteCodes.length) {
switch(byteCodes[pc++]) {
case ByteCode.ILOAD:
executionStack.push((int)byteCodes[pc++]); break;
case ByteCode.IMUL:
executionStack.push(executionStack.pop() * executionStack.pop()); break;
case ByteCode.IDIV:
{
int rval = executionStack.pop();
int lval = executionStack.pop();
executionStack.push(lval / rval); break;
}
case ByteCode.IADD:
executionStack.push(executionStack.pop() + executionStack.pop()); break;
case ByteCode.ISUB:
{
int rval = executionStack.pop();
int lval = executionStack.pop();
executionStack.push(lval - rval); break;
}
}
}
return executionStack.pop();
}
THIS IS A JVM LIKE
INTERPRETER CODE -
IT IS SMELLY, AND WHEN IT
EVOLVES, IT WILL STINK!
9. EFFECTIVE APPROACH
CODE SMELLS -> REFACTORING
THIS IMPROVED SOLUTION
USES COMMAND PATTERN
class Interpreter {
public int interpret(ByteCode[] byteCodes) {
Stack<Integer> evalStack = new Stack<Integer>();
for(ByteCode byteCode : byteCodes) {
byteCode.exec(evalStack);
}
Arrays.stream(byteCodes).forEach(byteCode -> byteCode.exec(evalStack));
return evalStack.pop();
}
}
abstract class ByteCode {
abstract void exec(Stack<Integer> execStack);
}
class ILOAD extends ByteCode {
byte val;
public ILOAD(byte arg) {
val = arg;
}
public void exec(Stack<Integer> execStack) {
execStack.push((int) val);
}
}
class IADD extends ByteCode {
public void exec(Stack<Integer> execStack) {
execStack.push(execStack.pop() + execStack.pop());
}
}
class IMUL extends ByteCode {
public void exec(Stack<Integer> execStack)
{
execStack.push(execStack.pop() * execStack.pop());
}
}
class ISUB extends ByteCode {
public void exec(Stack<Integer> execStack) {
int rval = execStack.pop();
int lval = execStack.pop();
execStack.push(lval - rval);
}
}
class IDIV extends ByteCode {
public void exec(Stack<Integer> execStack) {
int rval = execStack.pop();
int lval = execStack.pop();
execStack.push(lval / rval);
}
}
10. EFFECTIVE APPROACH
DESIGN & ARCH SMELLS -> RESTRUCTURING
Step 1: Separate the interface & implementation
Step 2: Depend on the interfaces (and not on the implementations)
(Reflection: Needs code-level changes)
11. EFFECTIVE APPROACH
DESIGN & ARCH SMELLS -> RESTRUCTURING
Step 1: Create a separate package for the interface(s)
Step 2: Separate the interface & implementation
(Reflection: Needs package-moves - no code level changes)
12. EFFECTIVE APPROACH
DESIGN & ARCH SMELLS -> RESTRUCTURING
Step: Separate the interfaces in “maven-classrealm” from the
implementation (concrete classes) into to a separate module
(“maven-classrealm-api”)
(Reflection: NO CODE CHANGE(S) NEEDED!!)
14. KEY BENEFITS
Quickening mental
models when working
with code
The time to understand the
structure of a large codebase
(especially for teams who
have been transferred
responsibility of an existing
product) is drastically
reduced
The cost of transfer of
maintenance is lesser as KT
time reduced as much as
75%
Modularization results
in reduced change
impact
The time to make a change
(new enhancement/bug fix)
is drastically reduced as the
time to do impact analysis is
drastically reduced
The bug density is lower as
developers can better
modularize and make
changes confidently
Improved build &
subsequent testing
time
Modularization results in
reduced time for compilation
& more modular components
as part of a modular build
system
Faster build times (even
upto 90%) resulting in
quicker turnaround time for
developers for changes +
quicker unit testing