Maintaining Your Code Clint Eastwood Style


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

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Clint Eastwood as "Blondie": The Good (a.k.a. the Man with No Name), a subdued, cocksure bounty hunter who teams with Tuco, and Angel Eyes temporarily, to find the buried gold.
  • “An artist wants to make a sculpture, but before she makes the sculpture, she just massages the clay. She starts towards making the sculpture, and sees what the clay wants to do. And the more she handles the clay, the more the clay tends to do what she wants. It becomes compliant to her will. A development team works on a piece of code over several months. Initially, they make a piece of code, and it's a little stiff. It's small, but it's still stiff. Then they move the code, and it gets a little easier to move”
  • 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. So we'll take that simplicity and we'll lose some of it. But tomorrow, hopefully tomorrow's program is as simple as possible for tomorrow's needs. Hopefully we'll preserve simplicity as the program grows.
  • When you have both firmly under your belt, that’s real power…
  • What should defaults be?Port 80The dilemma: Is default behavior always the right thing? Or a cop out?OK…if all else fails and you have no recovery possible (or you think this can never happen)….just swallow it and break the prime direct
  • Lee Van Cleef “Angel Eyes”
  • carefully constructed bad code that students rework….
  • As I told you in our skype meeting, things should be visible to senior management, and they have to take decisions accordingly. If, for example, they decided to dedicate 5% of the team buffer for refactoring, and the team find that they will probably need 2 years to complete the next stage of refactoring. In this case, it is up the the manager to either increase the percentage of time dedicated for refactoring or do whatever other solution they find appropriate. In my case, the solution was to reduce the amount of code to work on. In stead of working on the whole 1.7 million lines of code, we chose to work on the core and most important one, whic his 500 thousands LOC only.  I am not sure what you mean by team buffer. I can guess, but is that the “extra time” allowed for work? If so, what % of time is minimal to do refactoring work, do you think? It can’t be too minimal or the team will not make enough progress.
  • . They worked on reducing exact duplicates for two iterations  and managed to reduce code by another 20 KLOC.Comments and empty lines were not counted. Duplicate code is measured for level 1 (exact), or level 2 (changed variable names). Only un-gapped code clones are counted.
  • Touco
  • control of the telescope cannot be done by pushing off of anything. All motion must be controlled inertially: there are three "reaction wheels" -- 40 lb wheels that accelerate to change the momentum (equal and opposite reaction) of the telescope and point it at a new location or correct for wind shear.  Additionally, one 26 lb lead brick moves to adjust the balanceIt has an array of sensors on the telescope -- a magnetometer for gross pointing relative to Earth's field, an accelerometer, three cameras for determining position by the stars, and a gyroscope -- and another set on the fixed gondola -- GPS, accelerometer, magnetometer.  These are read at 10 Hz and processed through a Kalman filter with a 2 s memory (smoothing and looking for outliers due to noise) to determine pointing and motion, accounting for the reliability and accuracy of each instrument.  The pointing algorithm computes the necessary wheel torque to correct the pointing of the telescope, and the lead bricks move to zero the inertia of the entire telescope (including the reaction wheels).  If updated data miss the 10 Hz clock of the main pointing algorithm (sensors or actuators), the value from the preceding 0.1 s cycle is used.  The entire system communicates via a wireless/ethernet mix (no wires pass between the telescope and the balloon gondola).  The gondola carries the omin-directional antennas, which give us a 100 kB/s downlink but somewhere under 10 bytes/s (I think it is 2) uplink.InFOCuS continues to develop. X-Calibur, a new instrument that will detect the polarization of X-ray, will fly by 2014. And the InFOCuS team is investigating long-duration flights that would be launched from Antarctica, where wind patterns and 24-hours-a-day summer sunlight make the long flights possible. Thirty-day flights have occurred and a newly designed high-pressure balloon might stay aloft for more than one hundred days. That, notes Ramsey, could make them a low-cost competitor with orbital missions.A new pointing system that can keep balloon-based telescopes aimed at distant objects with unprecedented accuracy should also contribute to the scientific value of future missions. The Wallops Arc Second Pointer (or WASP), developed at NASA’s Wallops Flight Facility, can steadily aim a telescope at an object or area a single arc-second wide. Ramsey hopes that WASP will be used on a future “Super-HERO” mission.
  • Going with the flow can make things worseBut sometimes it is the only reasonable thing to doIt’s easier to clean up as you goBut not impossible to clean up later (just much, much harder)Challenge yourself and your team
  • Maintaining Your Code Clint Eastwood Style

    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 = ""; new User(username, password, emailAddress); } @Test public void testEmailAddressMustIncludeAtSign(){ String username = "rebeccawb"; String password = "abAB123~"; String emailAddress = ""; 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
    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
    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
    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.
    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