FoneMonkey 0.7.1 Released

Posted in Stu Stern on March 16th, 2010 by Stu Stern

Stu Stern originally posted this on Big Gorilla - Stu Stern's Blog.

Thanks to the intrepid souls who have taken the plunge into the first ever public release of FoneMonkey, we got some great feedback from 0.7 users and have just posted a new release, 0.7.1, with several important bug fixes and feature enhancements, notably:

Enhancements:

* WaitFor command - You can cause script execution to wait for views to be created or property values to be set
* "Popup Dialog" handling - You can now script UIAlertViews and UIAlertSheets.
* Better control event handling for UITextFields.

Bug fixes:

* OCUnit runner crash
* UINavigationBar rightItem playback


FoneMonkey 0.7.1 can be downloaded from FoneMonkey Project Home. We can understand if you considered 0.7 to be too early a release to try, but surely you'll feel differently about 0.7.1, which includes not one, but two, decimal points.

In all honesty, FoneMonkey, while not yet bug free, is quite stable and will definitely make your life better, at least in terms of iPhone testing.

31 Snowclones About Software Development

Posted in Dave Rodenbaugh on March 16th, 2010 by admin

admin originally posted this on Lessons of Failure.

Proving once again that my sense of humor is only funny to me, I bring you 31 snowclones about Software, Computers and Technology.

No, I’m not talking about snocones although I’m sure there are more than 31 flavors, most of them horrible like Bertie’s Everflavor Beans (One vomit snocone please!).

Snowclones are just a reference to a cliche that has been slightly altered for a new situation, like “In a Panic Room, no one can hear you scream!”, referencing the 1979 Alien movie tag line, “In Space, no one can hear you scream.”  The snowclone here is “In X, no one can Y.”

For your amusement, I’ve collected these snowclones relating to software.  Enjoy and add any others you’ve heard in the comments…

  1. Ruby/OCaml/Haskell/Python is the new Java.
  2. Manual? We don’t need no stinking manual!
  3. Or the new one for interpreted languages: Compilers?  We don’t need no stinking compilers!
  4. This is your brain.  This is your brain on perl.  Any questions?
  5. GOTO Considered Harmful” Considered Harmful’ Considered Harmful?
  6. Bastard Operator from Hell
  7. I’m not an ISO-9000 certified tester, but I play one at my day job!
  8. If you’re a Sun employee: I, for one, welcome our new Oracle overlords.
  9. Or, if you’re a Yahoo employee: I, for one, welcome our new Microsoft overlords.
  10. Untested code is the dark matter of software.
  11. Lines and Transfers and Bits, oh my!
  12. I’m in ur source codez, fixin ur bugz
  13. Open is the new closed
  14. These are not the MacBooks you’re looking for (wave hand while saying it)
  15. Data synchronization is hard.  Let’s go shopping!
  16. Or maybe, LISP is hard.  Let’s go shopping!
  17. What Would Bill Gates Do?  (WWBGD)
  18. If Linux is wrong, I don’t wanna be right.
  19. Whatever flips your bits.
  20. Got root?
  21. There’s no place like 127.0.0.1
  22. Don’t hate me because I’m a DBA.
  23. Dammit Jim, I’m an architect, not a project manager!
  24. There’s no crying in Cocoa Touch Development!
  25. And by “*” I mean “gets around 760,000 hits on Google.”
  26. Eric Raymond is the Margaret Mead of the Open Source movement
  27. One bitchin, fully-debugged algorithm does not an releasable application make.
  28. Linux developers are from Mars, Windows Programmers are from Venus.
  29. Rabid Atheistic Hackers for Jesus.
  30. If I had a nickel for every Haskell program I could find, I’d be broke.
  31. Holy segmentation fault, Batman!

No related posts.

Programming in the Small

Posted in Eric Daugherty on March 10th, 2010 by admin

admin originally posted this on EricDaugherty.com.

I believe that successful application development today is about 'tweaks and sub-features', not major functionality.  But my thought process for this post was kicked off by an interesting post by Mike Taylor: 'Whatever happened to programming?' Mike laments the evolution of programming.  He is nostalgic for the day when writing a program was about creating something from scratch, instead of assembling the various pieces.

I think his assessment of the transition is fairly accurate.  The number of frameworks and libraries in projects today far exceeds the number used in projects 5, 10, or 20 years ago.  This is especially true in the Java world, where build tools like Maven gained traction because they handled the dependency management.  And now, a non-trivial Java project can easily incorporate 100 or more jar files, while a trivial 'boiler plate' web application can easily have 20 jars.

In many ways this is frustrating.  It has also given rise to what I call the cut and paste programmer.  You can actually achieve reasonably impressive results simply by searching for the right libraries, and them assembling them together but cutting and pasting example code found via Google.

From a business perspective, these are all good things.  The level of skill required to produce results is lower, and the speed of development has greatly increased.  We are standing on a very tall foundation.

This also means that the major functionality of many applications is provided mostly by libraries and frameworks.  The heavy lifting parts are not really heavy anymore.  I think Jeff Atwood hits this nail on the head when he stated on a Stack Overflow podcast episode that the major features of Stack Overflow themselves are fairly trivial.  The real value is that it is a collection of many small features and tweaks that make the overall system successful (I can't find the reference, so I apologize if I paraphrased incorrectly).  I think this point is right on.  Most major 'features' are trivial to implement today using the rich set of libraries that exist.  Building a website that has questions an answers like stack overflow is trivial.  Making it successful is hard.  And the difference is all in the fine print.

Jeff discussed at some length the time they spent with the syntax parser (markdown) and instructions on the 'ask a question' page.  Small changes to how the information is displayed and highlighted are much more important then the major feature of saving the question to a database and displaying it.

Successful applications today are about the user experience.  There are very few applications that are truly innovative themselves and could not be replicated by gluing together a set of frameworks.

Real innovation today is in the small.  This is also why I believe that the rise of Appliance Computing is here, and that Write Once Run Anywhere languages are inferior to native client applications.  It is the difference between a iPhone web application and a native app.  They both have the same features, but the experience can be very different.  In the end, the real value is the small efficiencies in the application, not the large features. 

Web File Extensions are Bad

Posted in Eric Daugherty on March 4th, 2010 by admin

admin originally posted this on EricDaugherty.com.

I hate file extensions on websites.  It is an unnecessary leaky abstraction, and is discouraged by the W3C.  All file extensions are not necessarily bad, but any file extension that exposes the underlying technology implementation is.  Any .pl, .php, .asp, .aspx, .jsp, .do, *.struts, etc extensions are B A D.

I've talked about this before, and come up with some workarounds to build extension-less java web applications before.

However, I've come across what I think is a better way, thanks to a post a couple years ago by Matt Raible.

I came across the issue using the Spring WebMVC DispatcherServlet.  I want all dynamic URLs handled by the Spring Controllers, using Annotations.  However, mapping the DispatcherServlet to /* means that every URL will be processed by the DispatcherServlet, including the .jsp views returned by the Controller.  As I mentioned in the previous post, you can 'unmap' content and have it handled by a default or jsp servlet in some app servers, but not all.

You can also try to map specific subsets of URLs to spring.  However, this is harder than it sounds.  By default, Spring will map the wildcard portion of the URL matched, not the entire URL.  So if you have /foo/* as your url-pattern, the controller with a mapping of /foo/one will not match /foo/one, but instead matches /foo/foo/one.

It appears that you can use the property alwaysUseFullPath to change this behavior, but it did not seem to work as expected for me.

Instead, there is a more generalized solution, as Matt suggested.  URL Rewriting.

The URL Rewrite Filter project provides a Filter that you easily define rewrite rules for, just like in Apache.  So I setup my DispatcherServlet to match *.spring, I setup a rule to rewrite all extension-less requests to .spring, and I setup my annotations to have the .spring extension.

Now my web application can handle the odd html, png, or other static files if necessary, but does not expose any implementation details in the URLs.  Perfect.

For reference, here are the relevant portions of my config:

Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>SpringMVCServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/your.config.file.location.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SpringMVCServlet</servlet-name>
<url-pattern>*.spring</url-pattern>
</servlet-mapping>

</web-app>
urlrewrite.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
"http://tuckey.org/res/dtds/urlrewrite3.0.dtd">

<urlrewrite>
<rule>
<from>/$</from>
<to type="forward">home</to>
</rule>

<rule>
<from>^([^?]*)/([^?/\.]+)(\?.*)?$</from>
<to last="true">$1/$2.spring$3</to>
</rule>

</urlrewrite>
Sample Controller
@Controller
@RequestMapping("/foo")
public class SimpleDataController {

@RequestMapping("/bar.spring")
public ModelAndView bar() {
...
}
}

Annotating Custom Types in Hibernate

Posted in Jerry Andrews on March 3rd, 2010 by admin

Hibernate has a lot of nice features, and it’s pretty well documented, but a recent need to add a simple custom type to an existing mapping left me flailing around for documentation on exactly how to do it. I wanted to do it with annotations, not by updating the Hibernate configuration (that approach is well-documented). Here’s how it’s done.

Two new classes are needed.  You can do it with one (and the Hibernate examples do it that way), but they really have different functions, so I coded them separately.

The first is the class you want to use for the column.  In my case, I needed a Date with no milliseconds, which is a thin wrapper over java.util.Date.  Here’s my class:

/**
 * Oracle stores dates in DATE columns down to the second; Java stores them to the millisecond.
 * This occasionally can confuse Hibernate as to what data are stale.  This class slices off
 * any milliseconds which might be present in its representation.
 */
public class DateNoMs extends java.util.Date {
    private static final long serialVersionUID = 1L;


    /** @see java.util.Date() */
    public DateNoMs() {
        super();
        long t = getTime();
        setTime(t – t%1000);
    }


    /** @see java.util.Date(long) */
    public DateNoMs(long time) {
        super(time – time%1000);
    }
    
    /**
     * @param value
     */
    public DateNoMs(Date value) {
        long t = value.getTime();
        setTime(t – t%1000);
    }


    /** @see java.util.Date#setTime(long)     */
    @Override
    public void setTime(long time) {
        super.setTime(time – time%1000);
    }
}

Straightforward, right?  Now, in my class, I have a field mapping:
    @Column(name = “PAYMENT_DATE”)
    private DateNoMs m_paymentDate;
Of course, this won’t run–Hibernate will gag on the mapping, because it doesn’t know how to map a JDBC DATE column to a DateNoMs–as one would expect.  There are two things we need at this point: first, an object which Hibernate can use to transform JDBC DATE into a DateNoMs, and an annotation pointing to that “Factory”.  The factory class is produced by implementing (in the simplest case) org.hibernate.usertype.UserType. Documentation in this interface is pretty thin, but there are good examples available in the Hibernate distribution. Here’s my implementation.  I’m greatly helped by the fact that my class (DateNoMs) is very close to java.util.Date, and java.sql.Date extends java.util.Date.

/**
 * Map “things” (currently Oracle Date columns) to the DateNoMs.
 */
public class DateNoMsType implements UserType {

    /** @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, Object)     */
    public Object assemble(Serializable cached, @SuppressWarnings(“unused”) Object owner) {
        return cached;
    }

    /** @see org.hibernate.usertype.UserType#deepCopy(Object)     */
    public Object deepCopy(Object value) {
        if (value==null)
            return null;
        
        if (! (value instanceof java.util.Date))
            throw new UnsupportedOperationException(“can’t convert “+value.getClass());
        return new DateNoMs((java.util.Date)value);
    }

    /** @see org.hibernate.usertype.UserType#disassemble(Object)     */
    public Serializable disassemble(Object value) throws HibernateException {
        if (! (value instanceof java.util.Date))
            throw new UnsupportedOperationException(“can’t convert “+value.getClass());

        return new DateNoMs((java.util.Date)value);
    }

    /** @see org.hibernate.usertype.UserType#equals(Object, Object)     */
    public boolean equals(Object x, Object y) throws HibernateException {
        return x.equals(y);
    }

    /** @see org.hibernate.usertype.UserType#hashCode(Object)     */
    public int hashCode(Object value) throws HibernateException {
        return value.hashCode();
    }

    /** @see org.hibernate.usertype.UserType#isMutable()     */
    public boolean isMutable() {
        return true;
    }

    /** @see org.hibernate.usertype.UserType#nullSafeGet(java.sql.ResultSet, String[], Object)     */
    public Object nullSafeGet(ResultSet rs, String[] names, @SuppressWarnings(“unused”) Object owner)
            throws HibernateException, SQLException {
        // assume that we only map to one column, so there’s only one column name
        java.sql.Date value = rs.getDate( names[0] );
        if (value==null)
            return null;
        
        return new DateNoMs(value.getTime());
    }

    /** @see org.hibernate.usertype.UserType#nullSafeSet(java.sql.PreparedStatement, Object, int)     */
    public void nullSafeSet(PreparedStatement stmt, Object value, int index)
            throws HibernateException, SQLException {
        if (value==null) {
            stmt.setNull(index, Types.DATE);
            return;
        }

        if (! (value instanceof java.util.Date))
            throw new UnsupportedOperationException(“can’t convert “+value.getClass());

        stmt.setDate( index, new java.sql.Date( ((java.util.Date)value).getTime()) );
    }

    /** @see org.hibernate.usertype.UserType#replace(Object, Object, Object)     */
    public Object replace(Object original, 
            @SuppressWarnings(“unused”) Object target, @SuppressWarnings(“unused”) Object owner)  {
        return original;
    }

    /** @see org.hibernate.usertype.UserType#returnedClass()     */
    @SuppressWarnings(“unchecked”)
    public Class returnedClass() {
        return DateNoMs.class;
    }

    /** @see org.hibernate.usertype.UserType#sqlTypes()     */
    public int[] sqlTypes() {
        return new int[] {Types.DATE};
    }

}
The core of this class is the two methods which get and set values associated with my new type: nullSafeSet and nullSafeGet.  One key thing to note is that nullSafeGet is supplied with a list of all the column names mapped to the custom datatype in the current query.  In my case, there’s only one, but in complex cases, you can map multiple columns to one object (there are examples in the Hibernate documentation).
The final piece of the puzzle is the annotation which tells Hibernate to use the new “Type” class to generate objects of your custom type by adding a new @Type annotation to the column:
    @Type(type=”com.gorillalogic.type.DateNoMsType”)
    @Column(name = “PAYMENT_DATE”)
    private DateNoMs m_paymentDate;

The @Type annotation needs a full path to the class that implements the userType interface; this is the factory for producing the target type of the mapped column.
If you’re going to use your new type in a lot of places, you can shorten the @Type annotation by doing a typedef; you can place this in package-info.java in any package you like (I put mine in the same package as the UserType class).  Here’s the line for the type defined above:
@TypeDefs(
  {
    @TypeDef(name = “dateNoMs”, typeClass = com.gorillalogic.type.DateNoMsType.class
  }) package com.gorillalogic.type;
Now my column annotation can look like this:
    @Type(type=”dateNoMsType”)
    @Column(name = “PAYMENT_DATE”)
    private DateNoMs m_paymentDate;

That should be enough to get you started. 

Developing a Google App Engine (GAE) app using Maven

Posted in Eric Daugherty on March 3rd, 2010 by admin

admin originally posted this on EricDaugherty.com.

If you want to develop a Google App Engine (GAE) application using Maven, you can either use the Maven plugin maven-gae-plugin, which requires non-trivial hacking on your pom.xml, or you can keep your pom clean and create a simple Ant script.

My pom is a simple web application pom, with no specific GAE configuration.  I then created a build.xml in my project root that looks like this:
<project>
<property name="sdk.dir" location="/opt/appengine-java-sdk-1.3.1" />

<import file="${sdk.dir}/config/user/ant-macros.xml" />

<target name="runserver" depends=""
description="Starts the development server.">
<dev_appserver war="target/yourappname-1.0-SNAPSHOT" />
</target>

</project>

Using this, you can run your application in the GAE sandbox without having it take over your pom.

You can also have the ant task perform a maven package to insure everything is updated by adding an exec target to the runserver task.

You can read more about the full range of Ant tasks available for GAE, but I found this simple script helpful to get up and running quickly in the GAE sandbox without much effort.

FlexMonkeyTM 1.0 GA Now Available From Gorilla Logic | Press Release

Posted in FlexMonkey on March 3rd, 2010 by jonr

jonr originally posted this on FlexMonkey :: Flex UI Testing Tool.

Open-Source Functional Testing Tool for Adobe Flex Applications – New and Improved!

Broomfield, CO – March 3rd, 2010 – Gorilla Logic, an enterprise IT consulting services firm known for its top consulting talent, today announced the availability of FlexMonkeyTM 1.0 GA. With a rapidly growing user base that recently surpassed 5,000 registered users, FlexMonkey is a de facto standard for automated functional testing of Flex applications. After eight months of maturing in a beta status, FlexMonkey is now available for production use by developers and quality assurance professionals everywhere.

“FlexMonkey has been invaluable for us in testing Adobe Air applications. It has found bugs and regressions on several occasions, preventing them from ever reaching customers. And…I had everything I needed to add FlexMonkey tests into our continuous integration environment,” said Michael Portuesi, Principal Engineer, Zoodles.com.

FlexMonkey is an open source testing tool providing record/playback functional testing of
Adobe Flex applications. FlexMonkey 1.0 GA delivers the features and improvements most
requested by the FlexMonkey user community including:

  • “Wait For” handling – removes reliance on the PauseCommand by defining conditions that pause a script until true
  • Fuzzy Bitmap Compare – the Verify command for bitmap images now allows for user controlled tolerances in color comparisons
  • Simplified Setup – new easier process for configuring FlexMonkey testing of an application
  • Compatibility – out of the box compatibility with multiple Flex SDKs (3.3, 3.4.1 and
    3.5)
  • Code Compiling – easier customization and compilation of FlexMonkey source

“FlexMonkey keeps me from becoming exactly that – a monkey chained to a desk manually testing our software,” said Max Cameron, Co-Founder, Big Bang Technology, Inc. “Simply put, FlexMonkey kicks ass. It softens the enormous overhead of functional testing, and more importantly, it let’s my engineers get some sleep at night.”

FlexMonkey is well-suited for use by both developers and QA testers. It provides for regression and functional testing, and can be run from popular build systems and continuous integration environments, like CruiseControl.

“FlexMonkey 1.0 GA marks a major milestone for the FlexMonkey open source project and the Flex community who have come to rely on it. We encourage all existing users to upgrade to this new version which paves the way for our upcoming FlexMonkey releases for Flex 4, as well as our FlexMonkey/Selenium bridge,” said Stu Stern, CEO, Gorilla Logic.

FlexMonkey 1.0 GA is available today at www.gorillalogic.com/flexmonkey. Gorilla Logic also offers complete FlexMonkey training and testing services. Customers interested in early access to FlexMonkey for the Flex 4 SDK and the FlexMonkey/Selenium bridge should contact Gorilla Logic at www.gorillalogic.com/who-we-are/contact.

About Gorilla Logic

Gorilla Logic is an enterprise application development services and consulting firm known industry-wide for providing “gorilla” consultants that can dramatically improve development team productivity. Gorilla Logic has long demonstrated their industry-leading expertise in enterprise Java / JEE development, Adobe Flex / RIA development, and mobile device application development, including the iPhone. Gorilla Logic has been engaged to ensure the success of their most mission critical projects by Fortune 500 companies as well as small and mediums-zed businesses and startups in industries ranging from financial services to entertainment to aerospace and the government sector.

Gorilla Logic also develops open source tools for Java, Flex, and iPhone developers.
FoneMonkey (www.gorillalogic.com/fonemonkey)
FlexMonkey (www.gorillalogic.com/flexmonkey)
MonkeyWrench (www.gorillalogic.com/monkeywrench)
OpenGXE (www.gorillalogic.com/opengxe)

For more information about Gorilla Logic, please visit www.gorillalogic.com or email info@gorillalogic.com.

###

Gorilla Logic Media Contact
Chad Sanderson
303.974.7088 ext. 7002
chad.sanderson@gorillalogic.com

Older Developers: Bad Habits Are Killing Your Career

Posted in Dave Rodenbaugh on March 2nd, 2010 by admin

admin originally posted this on Lessons of Failure.

OK, so my last post about five pervasive myths about older software developers was definitely getting a lot of:

Who do we appreciate?

Go old guys!

“Old guys! Old guys! Rah-rah-rah!”

in the comments.  And it wasn’t necessarily undeserved…after all, it was about debunking myths that have crept in as supposed truisms over the years.  But I left out a tiny little detail about something important.

Older developers are killing their careers from their bad habits.

Sorry, the sound you just heard is your jaw hitting the keyboard.  “What!?  But Dave, you said experience was valuable and…”

Yes, I know what I said.  And I meant it.  Every word.  But there is one distinct advantage the younger set has over us fogeys:  they haven’t formed as many habits yet.

I’m not talking about a $5,000-a-day-hooker-and-blow* kind of habit.  I’m talking about the practices that you’ve codified into your daily routines as a developer since you started.  Like your (in)ability to write clear, concise comments.  Or comments at all.  Your constant lack of communication with other team members when you’re making major changes because you don’t think it’s necessary.  Your refusal to write documentation.  Or your passive refusal to learn new technologies because you think you have enough information to do your job already.

These are all habits we’ve picked up over the years.  Some are good, like making sure you always have a bug tracking system in place, or using source control like a religion.  But not all of them are, like some I mentioned above.  If you’ve been developing for more than 10 years, you’ve got a mix of both.  Don’t kid yourself.  You get them out of sloth or complacency from doing the same things over and over.  You don’t bother changing them because well, they’ve worked just GREAT so far.  There’s no motive to change.  It’s Newton’s First Law of Motion applied to software learning:

Sir Issac Newton

Maybe I'll rest since I nailed that gravity thing...

“Any object in motion will tend to stay in motion; any object at rest, will tend to remain at rest, unless acted upon by an outside force.”

After you reach a certain level of competency, assuming you aren’t subject to the Peter Principle and haven’t been promoted out of your competence yet, your motives for advancement are reduced by your motives for maintaining the status quo.  You’ve been considered the Senior Software Engineer for 5 years now and you don’t want to become a Pointy Haired Boss anytime soon, so Senior Software Engineer looks like a happy place to stay.

Wrong. Dead wrong.

This kind of thinking is exactly what generates the age-based bias and discrimination on older workers.  An attitude of complacency gets you labeled as a slacker.  Being a slacker didn’t get you where you are today, so why would you suddenly think this change of strategy is a good idea?

As a younger worker, your mind was more a tabula rasa than the Statue of David.  Adding new habits was easy because everything was new to you–doing design, learning frameworks, figuring out how to estimate schedules.  You’re cutting a road in your mind with a wagon.  The first time is hard because the ruts for the wheels just aren’t there yet.  But every time you complete a project, your mind adds depth to the ruts in the road.  And after 10 years, that road is well traveled and harder to veer from.

Habits are hard to break, but not impossible.  Studies have shown that a new habit takes about nine weeks to take shape and really stick in your mind.  That means, on average if you’re really working at it, you can break around 5 bad habits a year, or add 5 new habits, assuming you want to focus all your extra effort into adding a single habit during a nine week period.

Think about it:  You can alter your habits such that every year, you spend the time to add 5 new technologies or practices to your repertoire, one about every 9 weeks. Been thinking about learning Agile?  How about picking up a new language?  Maybe changing source code repositories from CVS to Mercurial?  This is exactly how we can all stay relevant in the face of ever-changing technologies.

As far as the bad habits go, what kind of recommendation would you get from a colleague that saw you go from the least-documented code to the best in a 6 month time frame?  Wouldn’t that impress them enough to say, “Hey, that old dog can learn new tricks…I’ll be damned.”

If you’re under 30, don’t laugh too hard about these habit-breaking notes.  You’ll be here soon enough yourself.  Best to cultivate the good habits upfront so you can add more good ones rather than break the bad ones later.

OK, back to the cheering now…that was more fun anyway:

“Old guys! Old guys! Rah-rah-rah!”

* Although you probably want to stay away from the hookers and blow too.  I can’t see how that’s good for your career, either. No experience there, just sayin’.  :)

Related posts:

  1. Five Pervasive Myths About Older Software Developers