Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
MAINTAINING
YOUR CODE
CLINT
EASTWOOD
STYLE
REBECCA WIRFS-BROCK
REBECCA@WIRFS-BROCK.COM
@REBECCAWB
“Whatever
success I've
had is due to a
lot of instinct
and a little
luck.”
—Clint Eastwood
“Whatever
success I’ve
had is through
a lot of hard
work and a
little luck.”
—Rebecca Wirfs-Brock
THE
GOOD
“I like the notion of
working the
program, like an
artist works a lump
of clay..”
—Ward Cunningham
Subscription Quantity Interface:
Version 1
public interface SubscriptionQuantity {
public Integer getQuantity();
public St...
Abstract
class
Interface
Subscription Quantity Implementation:
Version 1
@Override
public TimeBasedQuantity add(TimeBasedQuantity amount) {
return ...
Subscription Quantity Implementation:
Version 1
public SubscriptionQuantity add(SubscriptionQuantity amount) {
if (amount....
The Current Definition
public interface SubscriptionQuantity {
public abstract Integer getQuantity();
public abstract Stri...
“So today, let's write a
program simply. But let's
also realize that
tomorrow, we're going to
make it more
complex, becaus...
A Story of Consistent Error Reporting
Evolution
• Sprint 1: Ad hoc error detection in domain entities and
services
– Somet...
Ongoing Evolution
• Sprint 3
– ErrorReporter
– ErrorReportingService abstraction
– All services designed to collect and re...
public class ErrorReportingService implements ErrorReporter {
private MessageHolder errorMessages = new MessageHolder();
p...
public Account createAccount(
User user,
String customerName,
PaymentType paymentType,
CheckPaymentDetails preferredChecki...
“Sometimes if you
want to see a change
for the better, you
have to take things
into your own
hands.”
—Clint Eastwood
The Broken
Window Theory
“If the windows
are not
repaired, the
tendency is for
vandals to break a
few more
windows.”
— Jam...
Small Irritations Corrected
@Test
(expected = IllegalArgumentException.class)
public void testEmailAddressMustIncludeAtSig...
A Rhythm of
Restructuring, Refactoring and
Ongoing CleanupDate: April 15, 2013 11:35:46 PM PDT
Refactored package structur...
“Good code is its
own best
documentation.”
—Steve McConnell
Well maybe….
public void setPassword(String newPassword) {
if (newPassword == null) {throw new IllegalArgumentException("T...
private void addBuildingNameFloorRoomNumberLine() {
String buildingName = address.getBuildingName();
String floor = addres...
“I tried being
reasonable. I
didn’t like it.”
—Clint Eastwood as
Dirty Harry
Do Style Guides
Help?
“Go ahead, make my day.”
—Clint Eastwoodin Sudden Impact
http://source.android.com/source/code-style...
“Respect your
efforts, respect
yourself. Self-
respect leads to
self-discipline.”
—Clint Eastwood
Import Statements
Style over Substance?
• Order:
– Android imports first
– Then third parties (com, junit, net, org)
– Fin...
Naming Conventions
Style over Substance?
The Rules
• Start non-public, non-static field
names with m.
• Start static field...
Field Definitions
Do options lead to inconsistency?
• Define fields in standard places
• Fields should be defined at the t...
Exception Handling Guidelines
Do options lead to inconsistency?
• Prime Directive: Never swallow an exception
• Don’t catc...
“I get lazy or
bored, even with good
intentions it’s hard to
be consistent
programming.
It’s easier to be a
consistent run...
“Our parting thought: BE CONSISTENT. If you're
editing code, take a few minutes to look at the
code around you and determi...
“But local style is also important. If code you add
to a file looks drastically different from the
existing code around it...
THE
BAD
HOW DO YOU LIVE SOMEONE
ELSE’S BAD CODE?
“My first impression was confusion.
Some of the code is procedural, some is object-
oriented, some global variables are us...
Making Sense of “Mess-View-Controller”
• This isn’t Model-View-
Controller
• Logic is mixed with
presentation in absurd
wa...
Knowing the code isn’t enough…
• You’ve got to crack how the database works
and is updated.
• It ties everything together
...
schemaball
Martin Langhoff
A Recipe for Making Changes
1. Figure out how a similar
activity is done
2. Then:
– Write a plugin (enrol or auth)
– Add a...
HOW DO YOU LIVE WITH YOUR
OWN BAD CODE?
“When inexperienced developers write bad
code, their code is just bad. It has few (if any)
redeeming qualities. It often m...
Refactoring Experience Report
Amr Noaman, Agile 2013
• Initial, approach with three teams:
– First, write system/functiona...
Refactoring Experience Part 2:
A more systematic approach
• Prepare code to be increasingly refactorable
through a simple,...
Measure Quick-Win Progress
Metric Description Unit of Measure Target/ Threshold
Code size Number of lines of
code excludin...
Refactoring “Rules”
• Refactor on the main branch
• Limit scope at first:
– do quick win refactorings only,
– then restruc...
Measure Examples
Removing dead code cost one team 36 hours,
and reduced the code size by 6.4%.
46% of their code was dupli...
New Wisdom:
Improvement is better than inertia
• Small improvements can lead to bigger ones
• Ongoing:
– Spend an hour or ...
“A good man
always knows his
limitations.”
—Clint Eastwood
as Dirty Harry in Magnum Force
Sustainable Refactoring
• 10 % or less of total time
• On the main branch
• With management approval/buy in
• With identif...
THE
UGLY
At a float altitude of 128,000 feet, the balloon inflates to 40 million cubic feet. Photo Credits: NASA/Nagoya University
...
Beautiful Technology, Ugly Code
• The code:
– Originally written in MATLAB
– Translated to C
– “Light” on comments
– Writt...
// ------------------------------------------------------------------------
// TN-4/27/12 New function to resynchronize
//...
void Controller(double tc, VECTOR *qc, VECTOR *wc, double ttag, VECTOR *qhat, VECTOR *what, RWDATA
*RWmeas, LADATA *LAmeas...
/ /% PID control
//tau_pid = CTRL.Jt * (CTRL.Kp.*ae + CTRL.Kd.*we + CTRL.Ki.*Y1) ;
for(i=0; i<3; i++)
ELV(&a, i+1) = CTRL....
Living with this Code
• The algorithms are the abstractions
• Currently worked on by one engineer
• Printfs for debugging
...
Technical Debt
“ It’s easy to accrue. It’s
hard to payoff. What’s
lacking is the enforcer from
the loan shark. And the
goo...
Living with the Ugly:
A Story of Extending JavaScript
How can you define new, compatible functions?
array.fill()—set a spa...
An Ugly Challenge
• slice is an existing function that takes a start
and an end-position argument.
• splice is an existing...
The Ugly Details:
Nothing is Consistent, Lots of Defaults
array.splice(startIndex, howManyToRemove, Optional
elements to a...
Fill
fill(value, start=0, count=this.length-start)
/*
Every element of array from start up to but not including start+coun...
Examples
aFloatArray.fill(Infinity); //Fill all elements of a Typed Array with a
value.
aFloatArray.fill(-1,6); //Fill all...
Design Rationale
• Placing the fill value first means no indices need to be
specified when filling all elements. (Reasonab...
Transfer
transfer(target=0,source=0, count=this.length-source)
/*
The sequence of array elements from source index up to b...
Examples
[0,1,2,3,4].transfer(0,2); //[2,3,4,3,4]
[0,1,2,3,4].transfer(2,0,2); //[0,1,0,1,4]
[0,1,2].transfer(1); // [0,0,...
Design Rationale
• I considered "copy" and "move" names. Copy seems confusing. People
might expect that anArray.copy(1,10)...
http://xkcd.com/844/
“Now remember, things look bad and it looks like
you’re not gonna make it, then you gotta get mean.
I mean plumb, mad-dog ...
Conclusions
• Over time, code gets more complex
• It’s easier to clean up as you go
• Hard, not impossible, to clean up la...
Maintaining Your Code Clint Eastwood Style
Maintaining Your Code Clint Eastwood Style
Upcoming SlideShare
Loading in …5
×

Maintaining Your Code Clint Eastwood Style

2,935 views

Published on

This talk, presented at I.T.A.K.E 2013, explores options for revising, repairing, and extending good, bad, and ugly code. It takes a hard look at some good, bad, and ugly code written by the presenter and others.

Most people work with code that has both good and bad parts. The challenge with such a mix is how to modify what’s there without damaging the good parts, making bad things worse, or ugly situations even uglier. Sometimes we get an opportunity to make major repairs. But more often, we can only make small, incremental improvements. What we can do is constrained by cost, consistency, and compatibility concerns. Living with good, bad, and even ugly code is never easy.

But as Clint Eastwood says, “Sometimes if you want to see a change for the better, you have to take things into your own hands.”

Published in: Design, Technology
  • Be the first to comment

Maintaining Your Code Clint Eastwood Style

  1. 1. MAINTAINING YOUR CODE CLINT EASTWOOD STYLE REBECCA WIRFS-BROCK REBECCA@WIRFS-BROCK.COM @REBECCAWB
  2. 2. “Whatever success I've had is due to a lot of instinct and a little luck.” —Clint Eastwood
  3. 3. “Whatever success I’ve had is through a lot of hard work and a little luck.” —Rebecca Wirfs-Brock
  4. 4. THE GOOD
  5. 5. “I like the notion of working the program, like an artist works a lump of clay..” —Ward Cunningham
  6. 6. Subscription Quantity Interface: Version 1 public interface SubscriptionQuantity { public Integer getQuantity(); public String getUnits(); public DeliveryBasis getDeliveryBasis(); public String toString(); }
  7. 7. Abstract class Interface
  8. 8. Subscription Quantity Implementation: Version 1 @Override public TimeBasedQuantity add(TimeBasedQuantity amount) { return new MonthQuantity(getQuantity() + amount.asMonths().getQuantity()); } @Override public TimeBasedQuantity add(TimeBasedQuantity amount) { if (amount.getUnits() == getUnits()) {return new YearQuantity(getQuantity() + amount.getQuantity());} else {return new MonthQuantity(asMonths().getQuantity() + amount.getQuantity());} } Adding to a Year returns a new Month Quantity unless units are Years Adding to a Month Quantity returns a new Month Quantity public abstract TimeBasedQuantity add(TimeBasedQuantity amount); Time Based Quantity declares add must be implemented by subclasses
  9. 9. Subscription Quantity Implementation: Version 1 public SubscriptionQuantity add(SubscriptionQuantity amount) { if (amount.getDeliveryBasis() != getDeliveryBasis()) {throw new IllegalArgumentException("Cannot subtract issues from time");} else return (add((TimeBasedQuantity) amount)); } public TimeBasedQuantity add(TimeBasedQuantity amount) { return new MonthQuantity(getQuantity() + amount.asMonths().getQuantity()); } Adding to a Year returns a new Month Quantity unless units are Years Adding to a Time Based Quantity public TimeBasedQuantity add(TimeBasedQuantity amount) { if (amount.getUnits() == getUnits()) { return new YearQuantity(getQuantity() + amount.getQuantity());} else {return new MonthQuantity(asMonths().getQuantity() + amount.getQuantity());} } Adding to a Month Quantity returns a new Month Quantity
  10. 10. The Current Definition public interface SubscriptionQuantity { public abstract Integer getQuantity(); public abstract String getUnits(); public abstract DeliveryBasis getDeliveryBasis(); public abstract String toString(); public abstract SubscriptionQuantity multiplyBy(int amount); public abstract SubscriptionQuantity multiplyBy(SubscriptionQuantity amount); public abstract SubscriptionQuantity subtract(int amount); public abstract SubscriptionQuantity subtract(SubscriptionQuantity amount); public abstract SubscriptionQuantity add(int amount); public abstract SubscriptionQuantity add(SubscriptionQuantity amount); public abstract boolean lessThan(SubscriptionQuantity amount); public abstract boolean greaterThan(SubscriptionQuantity amount); }
  11. 11. “So today, let's write a program simply. But let's also realize that tomorrow, we're going to make it more complex, because tomorrow it's going to do more” —Ward Cunningham
  12. 12. A Story of Consistent Error Reporting Evolution • Sprint 1: Ad hoc error detection in domain entities and services – Sometimes setters threw exceptions – Sometimes they did not check for anything (fail fast approach) – This was sprint 1 after all! • Sprint 2: Some design discipline, but maybe not all good ideas – Well-formed domain object constructors don’t allow invalid state – Attribute setter methods throw exceptions – Services catch errors and do their best to report them – Problem: Sometimes objects possibly can get into inconsistent states – Problem: Sometimes not all errors are detected (bailing early)
  13. 13. Ongoing Evolution • Sprint 3 – ErrorReporter – ErrorReportingService abstraction – All services designed to collect and report all errors detected during handling of a request • Sprint 4: – User error messages declared in an external resource – Ability to add parameter values into messages to improve logging and support localization • Coming soon??? – ErrorValidator classes (check for cross-attribute constraints)
  14. 14. public class ErrorReportingService implements ErrorReporter { private MessageHolder errorMessages = new MessageHolder(); public boolean hasErrors() {return (errorMessages.size() > 0);} public boolean hasErrorMessage(String errorKey) { return errorMessages.containsKey(errorKey);} public void addErrorMessage(String key, String message) { errorMessages.addMessage(key, message);} public String getErrorMessage(String errorKey) { return errorMessages.getMessage(errorKey);} public Set<String> getErrorMessageKeys() { return errorMessages.keySet();} /** * If any errors have been accumulated, throws MultipleMessageException holding * the corresponding error messages. * @throws MultipleMessageException */ protected void handleErrors() throws MultipleMessageException { if (hasErrors()) {MultipleMessageException exception = loadMultipleMessageException(new MultipleMessageException()); throw exception;} } /** * @param loadable - exception into which this service's error messages are to be copied. * @return MultipleMessageException holding the same error messages as this service. */ protected MultipleMessageException loadMultipleMessageException(MultipleMessageException loadable) { for (String nextKey : getErrorMessageKeys()) { loadable.addErrorMessage(nextKey, getErrorMessage(nextKey));} return loadable; } ErrorReporting Service
  15. 15. public Account createAccount( User user, String customerName, PaymentType paymentType, CheckPaymentDetails preferredCheckingDetails, CreditCardPaymentDetails preferredCreditCardDetails, Address shippingAddress, Address billingAddress) throws MultipleMessageException { // cross-field validation. verifyRequiredParameters(user,paymentType,preferredCheckingDetails,preferredCreditCardDetails,shippingAddress,billingAddress); handleErrors(); Account account = new Account(customerName); account.setUser(user); account.setPreferedPaymentMethod(paymentType); account.setCheckPaymentInfo(preferredCheckingDetails); account.setCheckPaymentInfo(preferredCreditCardDetails); account.setShippingAddress(shippingAddress); account.setBillingAddress(billingAddress); handleErrors(); // Persist. try {account = getRepository().add(account); } catch (AccountAlreadyExistsException accountAlreadyExistsException) { addErrorMessage(ACCOUNT_ALREADY_EXISTS_ERROR, accountAlreadyExistsException.getMessage()); } handleErrors(); return account; } Creating an Account
  16. 16. “Sometimes if you want to see a change for the better, you have to take things into your own hands.” —Clint Eastwood
  17. 17. The Broken Window Theory “If the windows are not repaired, the tendency is for vandals to break a few more windows.” — James Q. Wilson & George L. Kelling
  18. 18. Small Irritations Corrected @Test (expected = IllegalArgumentException.class) public void testEmailAddressMustIncludeAtSign(){ String username = "rebeccawb"; String password = "abAB123~"; String emailAddress = "rebecca.wirfs-brock.com"; new User(username, password, emailAddress); } @Test public void testEmailAddressMustIncludeAtSign(){ String username = "rebeccawb"; String password = "abAB123~"; String emailAddress = "rebecca.wirfs-brock.com"; expect(IllegalArgumentException.class); new User(username, password, emailAddress); } “Fixed warnings about unused variables by using JUnit 4 annotations for expected exceptions and by removing variables that are never used, because exception is expected in constructor.”
  19. 19. A Rhythm of Restructuring, Refactoring and Ongoing CleanupDate: April 15, 2013 11:35:46 PM PDT Refactored package structure to group classes into packages organized by layer of the architecture: domain, service, repository. Date: April 17, 2013 8:25:51 PM PDT Made class variables private Date: April 20, 2013 3:19:39 PM PDT Implemented AccountRepository. Refactored test data, so that it can be shared between tests. Cleaned up UserRepositoryStub. Changed comments in Account to Javadoc comments. Date: April 18, 2013 12:31:18 AM PDT Refactor error messages in Entity objects by using new MessageHandler class and ErrorReporter interface. Specify AccountService interface to create Account. Date: April 22, 2013 2:47:35 PM PDT Removed print statements from tests. Date: April 22, 2013 9:56:10 AM PDT Fixed password encryption by using a fixed salt generator and a digester class instead of using the standard password encryptors. In a production system we would use more complicating salting algorithms, but this demonstrates wrapping a library in a service
  20. 20. “Good code is its own best documentation.” —Steve McConnell
  21. 21. Well maybe…. public void setPassword(String newPassword) { if (newPassword == null) {throw new IllegalArgumentException("The password may not be set to null.");} newPassword = newPassword.trim(); if (newPassword.matches("[a-zA-Z]{1}")) { throw new IllegalArgumentException("The password must contain at least one alpha character.");} if (!newPassword.matches("^.*d.+$")) { throw new IllegalArgumentException("The password must contain at least one digit.");} if (newPassword.length() < 6 || newPassword.length() > 32){ throw new IllegalArgumentException("The password must be 6 - 32 character and contain at least one letter and one number.");} if (newPassword.matches("^.*s.*$")) { throw new IllegalArgumentException("The password must not contain space or non-printing characters.");} this.password = newPassword; } My exception messages documented the rules. Without them, I’d be lost. Regex expressions are not documentation, no matter how straightforward
  22. 22. private void addBuildingNameFloorRoomNumberLine() { String buildingName = address.getBuildingName(); String floor = address.getFloor(); String room = address.getRoomNumber(); Boolean buildingNameExists, floorExists, roomExists = false; buildingNameExists = buildingName.length() != 0; if (buildingNameExists){ builder.append(buildingName); buildingNameExists = true; } floorExists = floor.length() !=0; if (floorExists) { if (buildingNameExists) {builder.append(", ");} builder.append(floor); } roomExists = room.length() !=0; if (roomExists) { if (floorExists) { builder.append(", ");} else if (buildingNameExists) {builder.append(", ");} builder.append(room); } if (buildingNameExists || floorExists || roomExists) builder.append("n"); } OK…. Some “good” code is just tedious. But you can do things to make it more legible. Good code is not “beautiful code”. Do not confuse goodness with beauty.
  23. 23. “I tried being reasonable. I didn’t like it.” —Clint Eastwood as Dirty Harry
  24. 24. Do Style Guides Help? “Go ahead, make my day.” —Clint Eastwoodin Sudden Impact http://source.android.com/source/code-style.html
  25. 25. “Respect your efforts, respect yourself. Self- respect leads to self-discipline.” —Clint Eastwood
  26. 26. Import Statements Style over Substance? • Order: – Android imports first – Then third parties (com, junit, net, org) – Finally, java and avax • Formatting: – Alphabetical within each grouping, with capital letters before lower case letters (e.g. Z before a). – A blank line between each major grouping (android, com, junit, net, org, java, javax). • The fine print: – Use explicit class names instead of *
  27. 27. Naming Conventions Style over Substance? The Rules • Start non-public, non-static field names with m. • Start static field names with s. • Start all other fields with a lower case letter • Public static final fields ALL_CAPS_WITH_UNDERSCORES public class MyClass { public static final int SOME_CONSTANT = 42; public int publicField; private static MyClass sSingleton; int mPackagePrivate; private int mPrivate; protected int mProtected; }
  28. 28. Field Definitions Do options lead to inconsistency? • Define fields in standard places • Fields should be defined at the top of the file • Or before methods that use them OK, which is preferred?
  29. 29. Exception Handling Guidelines Do options lead to inconsistency? • Prime Directive: Never swallow an exception • Don’t catch Exception. Instead catch specific exceptions by name • Prioritized list of handling strategies: 1. Throw a new exception that matches your level of abstraction 2. Handle gracefully and when possible substitute an appropriate default 3. Catch and then rethrow a runtime exception (because your app should crash) 4. If you ignore an exception, say why in a comment
  30. 30. “I get lazy or bored, even with good intentions it’s hard to be consistent programming. It’s easier to be a consistent runner.” —Rebecca Wirfs-Brock
  31. 31. “Our parting thought: BE CONSISTENT. If you're editing code, take a few minutes to look at the code around you and determine its style.” —The Google App Style Guideline Authors
  32. 32. “But local style is also important. If code you add to a file looks drastically different from the existing code around it, it throws readers out of their rhythm when they go to read it. Try to avoid this.” —The Google App Style Guideline Authors
  33. 33. THE BAD
  34. 34. HOW DO YOU LIVE SOMEONE ELSE’S BAD CODE?
  35. 35. “My first impression was confusion. Some of the code is procedural, some is object- oriented, some global variables are used, and the database is a forest of related tables…. I’ve learned to read and write Moodle, to modify it and expand it with custom plugins, to debug it and figure out what is going on even when the log files aren’t helping at all.” -Olja Petrovic The Apprentice Coder’s Blog Living inside Moodle e-Learning Platform, source code, and DB
  36. 36. Making Sense of “Mess-View-Controller” • This isn’t Model-View- Controller • Logic is mixed with presentation in absurd ways • No one else thinks this is a problem What’s already there isn’t going to get fixed. Technically, it isn’t broken. Just bad or awkward or error prone or unreliable or not the way you’d do it “Norms weren’t established. Conventions (if any) grow organically.” “Not what I’m used to”
  37. 37. Knowing the code isn’t enough… • You’ve got to crack how the database works and is updated. • It ties everything together – Users and course activities and events – The platform itself – the plugins and blocks and bits and pieces and roles and contexts
  38. 38. schemaball Martin Langhoff
  39. 39. A Recipe for Making Changes 1. Figure out how a similar activity is done 2. Then: – Write a plugin (enrol or auth) – Add a question type – Add a custom user profile field type – Override a class or function – Hack the core • Safe/easy • Requires deeper knowledge • Done at your own risk
  40. 40. HOW DO YOU LIVE WITH YOUR OWN BAD CODE?
  41. 41. “When inexperienced developers write bad code, their code is just bad. It has few (if any) redeeming qualities. It often must be scrapped, or at a minimum refactored intensely.” -Brandon Savage Conventional Wisdom
  42. 42. Refactoring Experience Report Amr Noaman, Agile 2013 • Initial, approach with three teams: – First, write system/functional automated tests to enable safe refactorings – Then identify design patterns or any other best practice, and transform the code – Keep the codebase working and add features/fix bugs • Results: Failure, abandoned efforts, disappointment, frustration • Observations: – Refactoring objectives too vague – Automated tests were difficult to impossible to write for bad code – Poor planning and measurement – Went too deep with major refactorings without finishing – Lack of management support – Communication problems – Unsustainable development
  43. 43. Refactoring Experience Part 2: A more systematic approach • Prepare code to be increasingly refactorable through a simple, continuous, and sustainable set of refactorings
  44. 44. Measure Quick-Win Progress Metric Description Unit of Measure Target/ Threshold Code size Number of lines of code excluding comments and white space. KLOC Reaching a plateau for 2 or more iterations. Code Size Reduction Speed (CSRS) Number of lines of code removed per one hour of refactoring. LOC This metric should be stable. Ones it drops, it is an indicator to stop this type of refactoring Number of duplicated code lines Calculated by counting absolute number of lines which are part of at least one (10 LOC or more) duplicate. Absolute Reaching a plateau for 2 or more iterations. Note that not all duplicates can be removed.
  45. 45. Refactoring “Rules” • Refactor on the main branch • Limit scope at first: – do quick win refactorings only, – then restructure and write automated component tests • Time allocated to refactoring specified by the senior manager as a % of team effort for each iteration.
  46. 46. Measure Examples Removing dead code cost one team 36 hours, and reduced the code size by 6.4%. 46% of their code was duplicated! Refactoring for another project was more challenging due to complex and financially critical problem domain. However, they still removed 40 kloc in 113 hours.
  47. 47. New Wisdom: Improvement is better than inertia • Small improvements can lead to bigger ones • Ongoing: – Spend an hour or two “cleaning up” while freeing your mind to do “background” problem solving • Small improvements to do: – Write a test – Refactor a test – Revise a function – Change some code to work better – Factor into smaller chunks – Untangle overly-complicated logic – Remove dead code – …
  48. 48. “A good man always knows his limitations.” —Clint Eastwood as Dirty Harry in Magnum Force
  49. 49. Sustainable Refactoring • 10 % or less of total time • On the main branch • With management approval/buy in • With identified success criteria • Modest first, then more aggressive • Stop when you get diminished returns
  50. 50. THE UGLY
  51. 51. At a float altitude of 128,000 feet, the balloon inflates to 40 million cubic feet. Photo Credits: NASA/Nagoya University Thanks Ben Zeiger for sharing the code and his lab where they are building/testing and preparing for 2014 launch. Software to Control the NASA X-ray Telescope InFOCμs
  52. 52. Beautiful Technology, Ugly Code • The code: – Originally written in MATLAB – Translated to C – “Light” on comments – Written by electrical engineer/physics guy, not sw dev – Mega-big functions – Lots of calculations – The algorithms work – It’s timing and control code that has bugs – Consequences of bugs: not able to locate accurately, missed repositioning (leading to instability)
  53. 53. // ------------------------------------------------------------------------ // TN-4/27/12 New function to resynchronize // serial interface and return error code. PMDuint16 ethSerial_Resync(void* transport_data, PMDuint16 errcode) { HANDLE_TABLE *p = (HANDLE_TABLE *)transport_data; PMDuint16 synccode = ethSerial_Sync(transport_data); /* unsigned short status, statush, statusd, statusdf, mode; PMDGetEventStatus(p->axish, &status); if (status > 1){ char binary[17]; itoa(status, binary, 2); printf("%f: Host: %s Event Status: %sn", get_current_time(), p- >hostname, binary); PMDGetDriveStatus(p->axish, &statusd); itoa(statusd, binary, 2); printf("%f: Host: %s Drive Status: %sn", get_current_time(), p- >hostname, binary); PMDGetDriveFaultStatus(p->axish, &statusdf); printf("Host: %s Drive Fault Status: %#Xn", p->hostname, statusdf); if( statusdf > 0x01){ PMDClearDriveFaultStatus(p->axish); printf("%f: Drive Fault Status Clearedn", get_current_time()); PMDResetEventStatus(p->axish, 0); PMDRestoreOperatingMode(p->axish); } PMDGetOperatingMode(p->axish, &mode); itoa(mode, binary, 2); printf("Host: %s Operating Mode: %sn", p->hostname, binary); PMDGetInstructionError(p->axish, &statush); printf("%f: Host: %s Instruction Error: %#Xn", get_current_time(), p- >hostname, statush ); PMDResetEventStatus(p->axish, 0); } */ //JWM 5/3 Added to diagnose when resync occurs to try to understand why. printf("Resync: %s Due to: %#X , synccode: %#X @: %f n", p->hostname, errcode, synccode, get_current_time()); format_resync_pkt(p->hostname, errcode, synccode, get_current_time());
  54. 54. void Controller(double tc, VECTOR *qc, VECTOR *wc, double ttag, VECTOR *qhat, VECTOR *what, RWDATA *RWmeas, LADATA *LAmeas, PVDATA *PVmeas, VECTOR *tauw, VECTOR *dc, VECTOR *ddc, double *tauaz, double *wcaz, VECTOR *dcount, LFCMD *LFcmd, RWACMD *RWAcmd) { double R_d[9], Rt_d[9], g_eci_d[3], g_b_d[3], dmg_d[3], hw_d[3], hb_d[3]; double H_d[3], He_d[3], Y2_d[3], Z2_d[3], Y1_d[3], tempY2_d[3], d_d[3]; double dm_d[3], tauh_d[3], tempg_b_d[3], temp_what_d[3]; double qe_d[4], ae_d[3], we_d[3], temp_qconj_d[4], u_d[3], a_d[3]; double tau_pid_d[3], taugy_d[3], taugg_d[3], tauc_d[3], taub_d[3], tauwc_d[3], qhatp_d[4]; VECTOR g_ecf = {3, 1, PVmeas->p, 0}; //vector_alloc_from_array(3, PVmeas->p); MATRIX R = {3, 3, R_d, 0}; //matrix_alloc(3, 3); MATRIX Rt = {3, 3, Rt_d, 0}; // matrix_alloc(3, 3); VECTOR g_eci = {3, 1, g_eci_d, 0}; //vector_alloc(3); VECTOR g_b = {3, 1, g_b_d, 0}; //vector_alloc(3); VECTOR dmg = {3, 1, dmg_d, 0}; //vector_alloc(3); MATRIX Wb_w, Ww_b, Jt; VECTOR Ho, balancepoint; VECTOR hw = {3, 1, hw_d, 0}; //vector_alloc(3); VECTOR hb = {3, 1, hb_d, 0}; //vector_alloc(3); VECTOR H = {3, 1, H_d, 0}; //vector_alloc(3); VECTOR He = {3, 1, He_d, 0}; //vector_alloc(3); VECTOR Y2 = {3, 1, Y2_d, 0}; //vector_alloc(3); VECTOR Z2 = {3, 1, Z2_d, 0}; //vector_alloc(3); VECTOR Y1 = {3, 1, Y1_d, 0}; //vector_alloc(3); VECTOR tempY2 = {3, 1, tempY2_d, 0}; //vector_alloc(3); VECTOR d = {3, 1, d_d, 0}; //vector_alloc(3); VECTOR dm = {3, 1, dm_d, 0}; //vector_alloc(3); VECTOR tauh = {3, 1, tauh_d, 0}; //vector_alloc(3); VECTOR tempg_b = {3, 1, tempg_b_d, 0}; //vector_alloc(3); VECTOR temp_what = {3, 1, temp_what_d, 0}; //vector_alloc(3); VECTOR qe = {4, 1, qe_d, 0}; //vector_alloc(4); VECTOR ae = {3, 1, ae_d, 0}; //vector_alloc(3); VECTOR we = {3, 1, we_d, 0}; //vector_alloc(3); VECTOR temp_qconj = {4, 1, temp_qconj_d, 0}; //vector_alloc(4); VECTOR u = {3, 1, u_d, 0}; //vector_alloc(3); VECTOR a = {3, 1, a_d, 0}; //vector_alloc(3);
  55. 55. / /% PID control //tau_pid = CTRL.Jt * (CTRL.Kp.*ae + CTRL.Kd.*we + CTRL.Ki.*Y1) ; for(i=0; i<3; i++) ELV(&a, i+1) = CTRL.Kp[i]*ELV(&ae, i+1) + CTRL.Kd[i]*ELV(&we, i+1) + CTRL.Ki[i]*ELV(&Y1, i+1); matrix_mac(&Jt, &a, &tau_pid); //% gyroscopic torque //taugy = cross(what, H) ; vcross(what, &H, &taugy); //% gravity gradient torque //taugg = CTRL.cgg * cross(g_b, CTRL.Jt*g_b) ; matrix_mac(&Jt, &g_b, &taugg); vcross(&g_b, &taugg, &taugg); vector_scale(CTRL.cgg, &taugg); //% attitude control torque //tauc = tau_pid + taugy - taugg ; vector_add(&tau_pid, &taugy, &tauc); vector_sub(&tauc, &taugg, &tauc); //% commanded wheel torque, body axes //taub = tauh - tauc ; vector_sub(&tauh, &tauc, &taub); // Inject sine wave torque, body coordinates torque_injector(1, &taub); //% commanded wheel torque, wheel axes //tauw = CTRL.Ww_b * taub ; vector_clear(tauw); matrix_mac(tomatrix(3, 3, CTRL.Ww_b, &Ww_b), &taub, (MATRIX *)tauw); // Inject sine wave torque, wheel coordinates torque_injector(2, tauw);
  56. 56. Living with this Code • The algorithms are the abstractions • Currently worked on by one engineer • Printfs for debugging • Code commented out when no longer used • Get it working, get it working in the lab (not simulations) • Leave well enough alone… – No refactoring
  57. 57. Technical Debt “ It’s easy to accrue. It’s hard to payoff. What’s lacking is the enforcer from the loan shark. And the good business reasons you had for accruing it in the first place are likely still there when you have to decide if you are going to fix it or do something else. —Allen Wirfs-Brock, editor of the ECMAScript standard
  58. 58. Living with the Ugly: A Story of Extending JavaScript How can you define new, compatible functions? array.fill()—set a span of elements starting somewhere with a single value array.transfer()—move a span of elements from one position to another position within the same array
  59. 59. An Ugly Challenge • slice is an existing function that takes a start and an end-position argument. • splice is an existing function that takes a start and length argument. • indexOf is a function that returns either a position or -1.
  60. 60. The Ugly Details: Nothing is Consistent, Lots of Defaults array.splice(startIndex, howManyToRemove, Optional elements to add…) Adds/removes items to/from an array, and returns the removed item(s) start index can be negative If negative, count back from the end of the array end index is optional array.indexof(searchItem, startIndex) start index can be negative start index is optional returns -1 if item not found array.slice(startIndex, endIndex) Returns an array starting at the given start argument, and ends at, but does not include, the given end argument Searches array for specified item and returns its position. start index can be negative If negative, count back from the end of the array How many to remove can be 0. If zero, add elements at start index.
  61. 61. Fill fill(value, start=0, count=this.length-start) /* Every element of array from start up to but not including start+count is assigned value. Start and count are coerced to Number and truncated to integer values. Negative count is treated as if it was 0. Negative start values are converted to positive indices relative to the length of the array: if (start<0) start = this.length-start Reference to start and count below assume that conversion has already been applied If count==0 no elements are modified. If start+count>this.length and this.length is read-only a Range error is thrown and no elements are modified. If start+count>this.length and this.length is not read-only, this.length is set to start+count. Array elements are set sequentially starting with the start index. The array is returned as the value of this method */
  62. 62. Examples aFloatArray.fill(Infinity); //Fill all elements of a Typed Array with a value. aFloatArray.fill(-1,6); //Fill all elements of a Typed Array starting at element 6 with -1. aFloatArray(1.5, 1, 5); //Fill the first 5 elements of a Typed Array. [ ].fill("abc",0,12).fill("xyz",-1,12); //Create a regular array, fill its first dozen elements with "abc", and its 2nd dozen elements with "xyz”
  63. 63. Design Rationale • Placing the fill value first means no indices need to be specified when filling all elements. (Reasonable Defaults for optional arguments) • Follows start/count argument pattern similar to Array splice. (Compatible with some existing function) • I initially tried the slice argument style assuming that this would have more utility when combined with search functions that return array indices (indexOf, findIndex, etc). But they return -1 to indicate failure which isn't a good fit with the slice style. • The start+count model seems conceptually simpler to understand and specify.
  64. 64. Transfer transfer(target=0,source=0, count=this.length-source) /* The sequence of array elements from source index up to but not including source+count are transferred within the array to the span of elements starting at the target index. The length of the array is not modified. The transfer takes into account the possibility that the source and target ranges overlap. source, target, and count are coerced to Number and truncated to integer values. Negative source and target indices are converted to positive indices relative to the length of the array. If count==0 no elements are modified. If source+count>this.length a Range error is thrown and no elements are modified. If target+count>this.length and this.length is read-only a Range error is thrown and no elements are modified. If target+count>this.length and this.length is not read-only, this.length is set to target+count. Array elements are sequentially transferred in a manner appropriate to avoid overlap conflicts. If target<=source a left to right transfer is performed. If target>source a right to left transfer is performed. If a target element is encountered that cannot be assigned, a type error is thrown and no additional elements are modified. Missing elements are processed as if they had the value undefined. Sparse array "holes" are transferred just like for other array functions. The array is returned as the value of this method */
  65. 65. Examples [0,1,2,3,4].transfer(0,2); //[2,3,4,3,4] [0,1,2,3,4].transfer(2,0,2); //[0,1,0,1,4] [0,1,2].transfer(1); // [0,0,1,2] Int8Array.from([0,1,2]).transfer(1); //RangeError Int8Array.from([0,1,2]).transfer(1,0,2); // Int8Array 0,0,1 Int8Array.from([0,1,2]).transfer(0,1,2); // Int8Array 1,2,2
  66. 66. Design Rationale • I considered "copy" and "move" names. Copy seems confusing. People might expect that anArray.copy(1,10) might create a new array, essentially meaning the same thing as Array.from(anArray). Move suggests a transfer that removes the source rather than replicating it. "transfer” doesn't carry those connotations. • See the fill rationale for why the slice argument patterns isn't used. • Defaulting count to this.length-source seems easiest to explain. There might be some utility in making the default count be min(this.length- source,this.length-target) but it could cause problems. • I considered a final optional "fill" parameter which would provide a value to put into each source element after it was copied (taking overlap into account). This would make it more like a true "move" operation. However, I could not convince myself that there was enough utility to justify the added complexity. (Do the simplest reasonable thing)
  67. 67. http://xkcd.com/844/
  68. 68. “Now remember, things look bad and it looks like you’re not gonna make it, then you gotta get mean. I mean plumb, mad-dog mean. ‘Cause if you lose your head and you give up then you neither live nor win. That’s just the way it is.” -Clint Eastwood, The Outlaw Josey Wales
  69. 69. Conclusions • Over time, code gets more complex • It’s easier to clean up as you go • Hard, not impossible, to clean up later • Don’t alwaysdo the “easiest” thing • Prepare to do the greater good • Make your goal ongoing sustainability

×