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.”
5. “I like the notion of
working the
program, like an
artist works a lump
of clay..”
—Ward Cunningham
6.
7. Subscription Quantity Interface:
Version 1
public interface SubscriptionQuantity {
public Integer getQuantity();
public String getUnits();
public DeliveryBasis getDeliveryBasis();
public String toString();
}
9. 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
10.
11. 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
12. 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);
}
13. “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
14. 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)
15. 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)
16. 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
18. “Sometimes if you
want to see a change
for the better, you
have to take things
into your own
hands.”
—Clint Eastwood
19. 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
20. 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.”
21. 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
22. “Good code is its
own best
documentation.”
—Steve McConnell
23. 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
24. 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.
28. 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 *
29. 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;
}
30. 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?
31. 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
32. “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
33. “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
34. “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
37. “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
38. 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”
39. 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
41. 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
43. “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
44. 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
45. Refactoring Experience Part 2:
A more systematic approach
• Prepare code to be increasingly refactorable
through a simple, continuous, and sustainable
set of refactorings
46. 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.
47. 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.
48. 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.
49. 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
– …
50. “A good man
always knows his
limitations.”
—Clint Eastwood
as Dirty Harry in Magnum Force
51. 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
53. 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
54. 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)
58. 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
59. 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
60. 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
61. 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.
62. 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.
63. 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
*/
64. 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”
65. 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.
66. 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 */
68. 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)
70. “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
71. 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
Editor's Notes
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
http://www.brandonsavage.net/writing-intentionally-bad-code/Writes 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