I've seen projects with shiny, new code render into unmaintainable big balls of mud within 2-3 years. Multiple times. But regardless of whether it's the code base as a whole that's rotten, or whether it's just the UI and User Experience that needs a major overhaul: the question on rewrite vs refactoring will come up sooner or later. Based on years of experience, and a plethora of bad decisions cumulating into epic failures, I'll share my experience on how to have a code base that stays maintainable - even after years. After this talk, you'll have more insight into whether you should refactor or rewrite, and how to do it right from now on.
WSO2CON2024 - Why Should You Consider Ballerina for Your Next Integration
Old code doesn't stink - Detroit
1. OLD CODE DOESN'T STINK
Refactor or Rewrite
Martin Gutenbrunner
FORD FIELD, DETROIT – OCT 16, 2018
DEVELOPER AND OPS CONFERENCE
2. ABOUT ME
Interested in Software and Hardware.
Started coding at 14
Programmer, team lead, software architect, operator,
administrator
In pursuit of the right way to build software
Passionate about technology, and much more about the
people he's working with
Find me on Twitter: @MartinGoodwell
Considers himself a lucky guy
3. THIS WILL BE LIKE
• James Camerson's Avatar
• at least 50% of it is "heavily inspired by others"
• A river runs through it, feat Brad Pitt and Tom Skerrit
• the other 50% is real-life experience
• The Walking Dead
• we'll have a Walker
• Real-life Example: Classic ASP, 15 years later
• The basics – aka "Mastering the craft"
• The Magic Sauce. The Kool Aid. Mr Miyagi's ultimate wisdom.
5. WHAT A REAL REWRITE REALLY MEANS
• Netscape Navigator 6 goes public beta in Nov 2000
• last major release (4.0) released almost 3 years ago (there was no v5)
• " During this time, Netscape sat by, helplessly, as their market share
plummeted. "
6. SAME MISTAKES
• Borland Arago dBase for Windows
• Microsoft Access ate their lunch
• then they made the same mistake again in rewriting Quattro Pro from scratch
and astonishing people with how few features it had.
• Microsoft Pyramid Word for Windows from scratch
• Lucky for Microsoft, they had never stopped working on the old code base, so
they had something to ship, making it merely a financial disaster, not a
strategic one.
7. WHY?
It’s harder to read code than to write it.
- Joel Spolsky
https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/
8. What is usually wrong?
• Architectural problems
• Even fairly major architectural changes can be done without throwing away
the code. By just moving things around, cleaning them up, creating base
classes that made sense, and creating sharp interfaces between the modules.
• Because it's inefficient
• When optimizing for speed, 1% of the work gets you 99% of the bang
• The code is doggone ugly
• One project I worked on actually had a data type called a FuckedString
• So half the functions started with “_” and half with “m_”, which looked ugly.
• This is the kind of thing you solve in five minutes with a macro in Emacs, not
by starting from scratch.
9. When to Rewrite?
• Is your code closely tied to a certain lifecycle / framework?
• eg Reactive, Mobile Apps, ...
• No one can maintain the existing code
• obsolete technology
• everyone's scared by just thinking about touching the code
• Always ask yourself: does this need to be a full rewrite?
10.
11. MAKE or BUY?
• Make the parts that make your solution unique
• "Buy" the parts that are common
12. Online Shop, written in ASP, rendering XHTML
Back then, in development for 15 years
EXAMPLE: classic ASP
13. PROJECT SETUP
• Use-case: eCommerce
• close connection to a business backend system
• Native Windows desktop application, connected to database
• Core: classic ASP
• Database: Pervasive SQL
• the actual database from the business backend
• Interfaces to
• MSMQ for sending orders to the business backend
• COM+ for querying prices from the business backend
15. WE WANTED TO REWRITE. WHY?
• 15 year old VB-Script codebase
• lack of structure
• hard to find VB-Script talent
• not up to today's standards (eg Unit Testing)
• Too closely bound to the business backend system
• Major updates of the system locked the database and stalled the online shop
16. CUSTOM BRANCHING
<%
if nCategoryId = 0 and (hostInfo.Path = "clayshop" or hostInfo.Path="bikershop" or hostInfo.Path="steelshop") then
out GetPageHTML(1,"de")
end if
%>
• Individual code branches for most tenants (~30 of them)
• If a tenant canceled his contract, the codebase usually was not
cleaned
17. RENDERING WHILE ITERATING RECORDSET
<%
set rsProd= oProduct.GetProduct(CLng(nProductId), CLng(nTenantId))
if not rsProd.eof then %>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td><img height="20" width="3" src="../../layout/pic/pix_tr.gif" border="0"></td></tr>
<tr><td class="productheadcolor">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="productheadiconcolor"><img height="20" width="20" src="../../layout/pic/icons/icon_kl_produkttip.gif" border="0"></td>
<td><div class="productheadcolor"> <%= getText("product") %>:</div></td>
</tr>
</table>
<% if (sTenantPath <> "that_special_shop") then DrawProducts rsProd, "productreplacement" end if %>
<% end if
rsProd.close
set rsProd= nothing
%>
• No separation between data-access, business logic, and UI
• Made it hard to see what really needs refactoring
18. OUR PLAN WAS
• 100% re-write in Java
• Create a separate database for the eCommerce part
• Move all tenants to the new codebase within six months
20. LEARNINGS
• 100% re-write in Java not possible
• no stable Java libraries for MSMQ and COM+ at the time
• Planned timeframe (of course) didn't work
• first tenant went online after 9 months
• but it was using the new UI
• (so, the old solution was still running in production in parallel)
• Re-doing the UI turned out to be the hard part
• we still didn't have all tenants converted after 2,5 years
22. WHAT WENT WELL?
• Introduction of dedicated DB
• Having a separate Importer component
• We built deployment automation with Jenkins
• The ASP-bridge turned out to work really well
23. WHAT WE SHOULD HAVE DONE
• Identify the UI-part as the real problem
• impossible to see due to non-layered codebase
• Embrace the fact that we have a huge number of customers on the
"old" codebase and design the new system for multiple UI
technologies
• EOL the old codebase
• If customers want new features, migrate them over
• Not a full rewrite
24. INTRODUCING BFF
• BFF
• Backend-for-Frontend
• aka Edge-Service
• source: Sam Newman's Microservice book
• can be used for
• routing
• authentication
• filtering
26. BENEFITS OF DOING IT RIGHT
• Save months of efforts, porting the messy UI code
• Have ASP UI benefit from separate database
• Only have a single touchpoint with the business backend system
• for any client
• XHTML rendered by ASP
• HTML5 rendered by Spring MVC
• potential mobile apps
27. IT MIGHT SMELL, THOUGH.
MASTERING THE CRAFT
OLD CODE DOESN'T STINK
28. HOW CAN THAT EVEN HAPPEN?
• Bounded contexts
• Things built in-house that would have been readily available
• a lot of the mess happens in boilerplate
• Code that's not testable
• Code that's not clearly/properly structured
• Harder to read than to write, remember?
• Bugs due to premature optimization
• YAGNI – You aren't gonna need it
• I think we tought that we'll need this for that feature that's coming up.
29. BOUNDED CONTEXTS
Don't mix things that don't belong together
• Do you see what's wrong with the "UserAccount" table?
30. BOUNDED CONTEXTS
Wrong: sharing DTOs between different domains
Some services use same attributes, some use specific ones
CatalogService
ShoppingCart
Service
OrderService
ProductDto
* id
* name
* description
* userRatings
* imageUrls
* price
* vat
* quantity
31. BOUNDED CONTEXTS
Right: dedicated DTOs for each domain
but smells like duplication a lot, because of the names
better: each DTO only contains the attributes it needs
CatalogService
ShoppingCart
Service
OrderService
CatalogProductDto
* id
* name
* description
* userRatings
* imageUrls
* price
CartProductDto
* id
* name
* price
* quantity
OrderProductDto
* id
* name
* price
* vat
* quantity
32. BOUNDED CONTEXTS
Same DTOs, but different names. Much better fit to the domain.
CatalogService
ShoppingCart
Service
OrderService
ProductDto
* id
* name
* description
* userRatings
* imageUrls
* price
CartEntryDto
* id
* name
* price
* quantity
LineItemDto
* id
* name
* price
* vat
* quantity
33. NOT INVENTED HERE
• Focus on business logic
• and separate it from boilerplate
• Don't build what you don't need to
• Queues
• Connection Pools
• Anything you build needs to be maintained.
• The only thing you'd want to maintain is business logic.
35. CODE DUPLICATION
Wrong: use inheritance for saving number of attributes in classes
abstract class MasterPojo {
protected int id;
}
public class AnyPojo extends MasterPojo {
...
}
public class AnyOtherPojo extends MasterPojo {
...
}
36. CODE DUPLICATION
Right: don't mix unrelated objects
public class AnyPojo {
private int id;
}
public class AnyOtherPojo {
private int id;
}
37. CODE DUPLICATION
Code duplication really means that any code that makes decisions (eg
IF, loops, ...) should not be copy/pasted.
Example:
• We have integrated with MailChimp for sending Emails
• We also want to integrate with other messaging services
• So, we copy the code from our MailChimp integration, paste it into a
new package and just rename the classes and make changes
according to the new 3rd party there.
• Right way: identify common behavior. Have that in base-classes or a
library that can be used by both implementations.
39. DESIGN FOR TESTABILITY
• Try to be as atomic as possible (complex objects vs atomic)
• In Java: use package private instead of private
• Have methods do as little as possible
• Methods that do more should just call other methods
42. THINGS TO CONSIDER
• Integers: negative, ranges
• Strings: length, special characters, null, empty, case-sensitive
• Objects:
• all of the above, depending on member variables datatypes
43. TESTING CODE
A code tester walks into a bar.
Orders a beer.
Orders 2.15 billion beers.
Orders -1 beers.
Orders äüöß beers.
Orders a nothing.
Orders a cat.
Tries to leave without paying.
44. TESTABLE CODE
• every IF in a method requires a test for every branch
• that's why code inside blocks should go into a separate method
• allows to test that method independently
• any possible input parameters require a test
• any possible return values require a test
• the smaller your classes, the smaller your methods, the easier it is to
maintain test code
• the amount of test code can easily be equal to your "real" code
• any method should be testable independently
45. TESTABLE CODE
• Some relationships are hard to test.
public int calcIt(int a, int b) {
int one = CalcUtil.calcIt(a,b);
int result = doSomethingElse(one);
return result;
}
46. TESTABLE CODE
• Some relationships are hard to test.
public int calcIt(int a, int b) {
int one = CalcUtil.getInstance().calcIt(a,b);
int result = doSomethingElse(one);
return result;
}
47. TESTABLE CODE
• Any dependencies to other classes should be easy to change.
private CalcUtil calcUtil = CalcUtil.getInstance();
public int calcIt(int a, int b) {
int one = calcUtil.calcIt(a,b);
int result = doSomethingElse(one);
return result;
}
void setCalcUtil(CalcUtil calcUtil) {
this.calcUtil = calcUtil;
}
48. READABLE CODE
• Be as descriptive in your code as possible
public class Walker {
public int moveAround(int position, int angle) {
if (angle > 0 && angle < 180) { //go left
...
}
...
}
}
49. READABLE CODE
• Be as descriptive in your code as possible
public class Walker {
public int moveAround(int position, int angle) {
boolean goLeft = angle > 0 && angle < 180;
if (goLeft) {
...
}
...
}
}
50. READABLE AND TESTABLE CODE
• Be as descriptive in your code as possible
public class Walker {
public int moveAround(int a, int b) {
boolean goLeft = shouldGoLeft(a,b);
if (goLeft) {
...
}
}
boolean shouldGoLeft(int a, int b) {
return angle > 0 && angle < 180;
}
}
51. MEANINGFUL DOCUMENTATION
• Class-level comments to give context.
/**
* This is the entry class. You'll find most
* interesting entrypoints here.
* @see: ThatOther.class
*/
public class Lala {
}
54. PREMATURE OPTIMIZATION
• "Because we need the performance"
• We need to implement it in "D"
• Do everything in Stored Procedures (even simple CRUDs)
• You need to declare that variable outside of the loop to reduce the load on
the GC
• Specialize first, generalize later
• "I think we thought that we will need that for one of the screens."
• Release often
• finish one feature release it get feedback improve it
• The IT-world has changed. It's not about CPU-cycles anymore
• It's about codebases that can scale to a large number of programmers.
55. READ
• Find all this and lots more here:
• Clean Code, by
• Robert C. Martin
56. READ
• Find all this and lots more here:
• Growing Object-Oriented Software,
guided by Tests, by
• Steve Freeman, Nat Pryce
57. CAN WE USE SOME OF THAT FOR MOBILE OR DESKTOP?
THE PROPER MONOLITH
58. LAYERS
• One artifact per business domain
• 3-tiered architecture inside of each artifact
• Sharing of interfaces by means of –api artifacts
• No shared database schemas between domains
59. PROPER MONOLITH
.war file
catalog.jar cart.jar billing.jar order.jar
Controllers
Physical Database
Catalog
DB
Cart
DB
Billing
DB
Order
DB
catalog-api.jar cart-api.jar billing-api.jar order-api.jar