Thursday, December 27, 2007

Making Process Results Public

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/making_process_results_public.htm]

Enterprise applications require a lot of supporting processes - continuous builds, feature tracking, monitoring production, etc... One sign of a mature department is that the non-secure output from those processes is publicly available to the entire team, as opposed to "hidden" in a personal file on some manager's computer. This keeps everyone on the same page, encourages team feedback, and reduces bureaucracy.

 

For example, anyone on your team should be able to tell you the release dates, their current expected feature list, the amount of open issues they have, and if their code patch broke any unit tests on the server. Those should not be "special" things that only a manager has access to view.

 

There are several processes that any development department should have, and these processes should all have publicly accessible results (such as via a web dashboard or shared network drive).

  • Issue Tracking System (we use a web-based solution)

  • Continuous build with unit test results (we prefer CruiseControl)

  • Wiki (we started with FlexWiki to just get started, but now use SharePoint)

  • Production performance results (you should be able to go to a web dashboard, not pester your IT department for the raw IIS log files)

  • Available project plan (even an Excel sheet on a shared network drive)

There's no doubt more to this list, but it's a good start.

Wednesday, December 26, 2007

Book: Getting Started in Consulting

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/book_getting_started_in_consulting.htm]

I just finished a very good book - Getting Started in Consulting. As its title suggests, it explains how to start up your own consulting practice. While I am personally entrenched in industry at Paylocity and not heading towards consulting, the book is still very applicable. (I used to work in consulting at Deloitte and CSC). Essentially, the developers who really stand out do a lot of the same things that successful consultants do - they take initiative to add value in a way that supports the client.

 

As someone now in industry, my "client" is usually my boss, or the customers who purchase our product. My "fees" are usually how I get to spend my time - i.e. I need to justify to my boss that project X is worth the time investment, and they should therefore allot schedule resources for it. The chapters on sales and marketing are good for any developer to know because they remind you that ultimately the project needs to generate revenue. It's easy to think that money grows on trees for these corporate giants, but for many of us, good projects often come down to a series of small "sales". Alan does a great job of showing how to incrementally add value and handle scope creep.

 

He also has very applicable chapters on "Moving to the Next Level" and "What do you do with Success?" For developers, if you've been in the field for 10 years, are you just doing more same old features, or are you still professionally growing and contributing back to the community via mentoring, publications, user groups, or other extra curricular activities?

 

It's an easy read, a well done book, and stimulates your mind to think about how to add value for your current project in industry.

Monday, December 24, 2007

Merry Christmas

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/merry_christmas_1.htm]

Merry Christmas for 2007, and a happy new year coming up in 2008!

 

Wednesday, December 19, 2007

Generic Dictionary in C#

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/generic_dictionary_in_c.htm]

I love the generic Dictionary class. It's located in the System.Collections.Generic namespace. This class is like a strongly-typed hash table (which lets you reference objects as key-value pairs). You can specify the type for the key and another type for the value. This code snippet below shows how to create, add, and retrieve data from a Dictionary. It spares the developer from writing a bunch of extra conversion wrapping code.

    [TestMethod]
    public void Dictionary_1()
    {
      Dictionary<string, DateTime> group = new Dictionary<string, DateTime>();

      //Add based on separate variables
      string strKey1 = "abc";
      DateTime dt1 = DateTime.Now;
      group.Add(strKey1, dt1);

      //Add as single line
      group.Add("def", new DateTime(2005, 12, 25));

      //Should now have 2 items
      Assert.AreEqual(2, group.Count);

      //Get value given key
      DateTime dt2 = group["abc"];
      Assert.AreEqual(dt1, dt2);
    }

Generics are great. You can also use them for strongly-typed lists, setting the return type of a method, and many other techniques that simplify code.

Tuesday, December 18, 2007

Tips to overcome problems with learning

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/tips_to_overcome_problems_with_learning.htm]

Although it's common knowledge that you need to invest time and effort to learn, the question is how to maximize that investment. Certainly you will encounter problems at every step of the way, such as:
  • Staying motivated.
  • Not feeling overwhelmed.
  • Finding time to learn despite being busy.
  • Learning the right stuff.
  • Retaining what you do learn.
  • Getting to use your new hard-earned knowledge.

Here are some random tips to dealing with those problems. Obviously a lot more could be said about each one; this is just a partial brain dump - and isn't that what blogs are for?

ProblemIdeas for Solutions
Staying motivated
  • Work on what you personally care about
  • Reflect on your accomplishments
Not feeling overwhelmed
  • Make reasonable goals
  • Focus on smaller chunks
Retaining what you do learn
  • Summarize your new knowledge
  • Put it in easy-to-find places
  • Practice it hands-on
Learning the right stuff
  • Continually try at everything, and eventually you’ll hit the jackpot (This will also increase your learning ability such that you’ll be ready when you get the chance).
  • Invest time outside of your required learning to explore the topics you want.
Using your new hard-earned knowledge
  • Pick topics that assist with your personal goals, so regardless of what happens at work you'll still get to use it.
  • Hang out in communities that value the same knowledge.
     
Finding time to learn despite being so busy.
  • Use the dead parts of the day (like driving or waiting in line) to mull over ideas.
  • Use smaller chunks of time instead of waiting for an entire free day.
  • Even in seasons when you’re just trying to survive (and hence are not doing any new studying), you can still learn better character like patience and determination.

 

Monday, December 17, 2007

Twenty excuses to avoid learning

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/twenty_excuses_to_avoid_learning.htm]

I was thinking the other day of all the excuses that I've heard for my software developers avoid trying to learn new things. I'm a huge advocate of developers doing continuing education, so I always find the excuses interesting. Here's a random list of the top twenty:
  1. I’m too old / I’m too young.
  2. I’m not smart enough.
  3. I don’t need anything new / I don’t care.
  4. But my memory is too bad…
  5. Even if I learn something new, it won’t help me.
  6. I don’t have the technical job yet. First I’ll get the job, and then I’ll learn about it.
  7. The technology will just be obsolete by the time I learn it.
  8. I’ll just wait until some teaches me.
  9. Learning is punishment for being bad - i.e. the grade-school mentality when you're teach assigns extra homework to "punish" you.
  10. The instructor dislikes me, therefore I won’t try.
  11. The techniques/resources aren’t available.
  12. My environment is unfair, so why put myself on the line when it’s setup against me.
  13. Training is too expensive
  14. It’s too overwhelming
  15. It’s my boss’s responsibility to train me
  16. I don’t even like my job, why would I want to learn more about it? (Rebuttal: then learn about another job, the job you want).
  17. I don’t want to waste effort learning something unless I absolutely need it.
  18. If I learn some niche tech, then I’ll get stuck maintaining that obsolete system.
  19. I worked really hard before, and nothing good came from it.
  20. It’s not worth it.

Are there other ones that you've heard? At some point, I'd like to offer explicit rebuttals to each of these.

Sunday, December 16, 2007

Resources for learning software development

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/resources_for_learning_software_development.htm]

I was thinking the other day how many different resources are out there to help someone learn software development. There's something to appeal to almost every style:

  1. Books
  2. Reference docs
  3. Articles
  4. Webcasts
  5. Podcasts
  6. Virtual labs
  7. Newsgroups, discussion forums
  8. Live Events
  9. Professional Groups
  10. Blogs
  11. Training (class room setting)
  12. Sample code - quickstarts
  13. Sample code - real projects
  14. Tutorials & Walkthroughs
  15. Local class at community college
  16. Bachelors or Master’s degree
  17. Teaching others (forces you to learn it)
  18. Write your own blog
  19. Personal pet projects
  20. Open source projects
  21. Your coworkers. A physically present person, who can look at your machine environment, can be the best way to learn.
  22. Private tutor – perhaps for introductory topics where such tutors are much more abundant.
  23. Certification

Any others that you've personally found helpful?
 

Thursday, December 13, 2007

Architecture: Keeping it non-exponential

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/architecture_keeping_it_nonexponential.htm]

Systems have a tendency to get more disordered. Architecturally, it's easy to always take shortcuts such that your application code becomes one big mess of tightly coupled objects and duplicated code. It starts innocent enough -you have only a few initial components, and each one has a dependency on the others. So that's still only a few dependencies.

But then, a few emergencies later (and what isn't an emergency?), your code has ballooned, and the dependencies have exponentially exploded into a mess. The project has accrued so much technical debt that the schedule is hopeless, and it's no longer fun. Every object is intertwined with every other object:

A much more sustainable model is to demand cleaner code at each step of the way, and end up with something maintainable, which the number of dependencies increases linearly with the number of new components. This lets you add, remove, and modify the code much more easily:

I see this as one of the basic issues of architecture: keeping the ever-growing code base in a maintainable state. In application development, this often boils down to conscious decisions where everyone "knows" the right way, but instead write sloppy code for ("we don't have time", "we'll fix it later", "this one won't hurt", "it works", "the maintenance team will fix it", etc...). Often, it seems like the team just needs a strong and credible voice to encourage everyone to do the right thing. Especially for long-term, enterprise projects, this seems like the optimal way to go. It sure beats having 12 months of garbled code.

Wednesday, December 12, 2007

SSN is not a secure password

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/ssn_is_not_a_secure_password.htm]

I had a conversation the other day where SSN was considered viable to replace using a password. Yes, it simplifies single sign-in, but it has major consequences (and there are other ways to do single sign-in). Using SSN as a password is a bad idea, the kind of thing that blows up in your face with security and functionality problems.

  1. You can't change your SSN (unlike your password)

  2. SSN is too limited. It's merely a nine digit number, so it can't even pass strong password tests. Furthermore, the last 4 digits are often available (so down to 5 digits left), and there are rules that an SSN must adhere too (they aren't just any random 9-digit numbers). With less than 50,000 possibilities, it's fair game for a brute force attack.

  3. SSN is publicly available in many cases. For example, HR and managers can often see your SSN (but not your password).

  4. SSN has direct real life significance too, and is therefore immediately dangerous if lost. For example, if you lose a password, that's bad, but it's only dangerous if the hacker (A) knows the context - i.e. the system  to apply it to, and (B) the password hasn't changed yet. But the context for an SSN is immediately known (like any financial or government institution), and SSNs can't change.

What's your favorite Domain Specific Language?

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/whats_your_favorite_domain_specific_language.htm]

I love domain specific languages (DSL). The idea is that instead of programming at a low level (i.e. tedious and error-prone), you program at a higher level of abstraction. This means coding with a language (or even class library) that closely maps to the problem domain. For example, here are several domain specific languages. While you could try to do without, it just becomes so much easier with them:

 

DSLPurposeExampleManually doing without:
Regular ExpressionsFind and replace patterns in textFind all numeric decimals in a fileYou could use core string methods like SubString and IndexOf
SQLManage database dataSelect all employees that meet a certain criteriaYou could do selects by getting the entire dataset and cycling through the object model
XPathQuery xml dataGet the custom order xml nodes where price is less than $100You could step through the xml with a reader, or loop through an XmlDocument
MSBuildMicrosoft's build engine to automate your processesCompile your application on a build server, run unit tests, and then produce MSI outputsYou could use System.Diagnostics to manually run a bunch of commands, and keep track of error conditions and logging output yourself.
String Format ExpressionsFormat a string using var.ToString("myPattern")Format the number 12.3456 to only two decimal placesYou could use core string methods, and pick apart the variable, and re-assemble it.

 

The point is that while someone could get by without knowing the appropriate domain language, it's just not practical to tackle the domain without it. Each of these has tons of tutorials and quickstarts, so there's no reason to avoid them. An application developer should probably be comfortable with most of these.

 

So, what's your favorite domain specific language?

Tuesday, December 11, 2007

Silverlight Image Utilities - clipping and shrinking an image to fit

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_image_utilities__clipping_and_shrinking_an_imag.htm]

For my TruckWars game, I needed to get the profile shot for an image. For example, when you select an object, it displays an image of that object in the dashboard. The two problems are:

  1. What if the image is bigger than the dashboard's profile size? (i..e image is 96x96, but the dashboard only allows 48x48).

  2. What if the image has multiple frames for animation (i.e. image displays as 48x48, but has two frames, and therefore actual size is 96x48).

I wanted a way that given any image, it would clip the image to a single frame, and then shrink it to fit within the allowed-profile size. You can do this if you know the actual image size. In this case, the size of the profile image in the dashboard is 48 pixels. Because I make the animation frames be horizontal (i.e. something with two frames is twice as wide), I scale the image based on height. Then I clip to just the first frame.

 

    public static void MakeProfileImage(ref Image image1, Size szActual)
    {

      //Scale back the img to fit to the view size
      const double profileHeight = 48;
      double dblScale = szActual.Height / profileHeight;
      image1.Width = szActual.Width / dblScale;
      image1.Height = profileHeight;

      //Always clip the view size (in case there were multiple frames for animation)
      RectangleGeometry r = new RectangleGeometry();
      r.Rect = new Rect(0, 0, profileHeight, profileHeight);
      image1.Clip = r;
    }

 

I added this to a ImageUtilities class for reuse later.

 

Monday, December 10, 2007

Silverlight and Globalization: System.FormatException from NumberFormatInfo

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_and_globalization_systemformatexception_from_n.htm]

Through recent error-logging that I added to TruckWars, I found out the sometimes this line would fail:

 double x = Convert.ToDouble("12.5");

 

The input string was static (not user input) as it came from an xml config file. The line threw this exception:

System.FormatException: Input string was not in a correct format.
   at System.Number.StringToNumber(String str, NumberStyles options,
    NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
   at System.Number.ParseDouble(String value, NumberStyles options, NumberFormatInfo numfmt)
   at System.Double.Parse(String s, NumberStyles style, NumberFormatInfo info)
   at System.Convert.ToDouble(String value)

At first I thought "of course '12.5' is a valid double." And of course it worked on my machine, and all the servers I was checking. But, the logs still showed this occasional error. Then I thought - what if it's a globalization problem? In other words, for the "en-US" culture, "12.5" is a valid number, but not for other cultures. For example, this would fail for someone in France, where "12,5" is used for a decimal place (note the comma instead of the period).

 

So, I set up a unit test to check for a different culture:

[TestMethod]
public void ParseFromString_Global_Decimal()    
{      
  System.Globalization.CultureInfo culture =
  System.Globalization.CultureInfo.CreateSpecificCulture("fr-FR");
  System.Threading.Thread.CurrentThread.CurrentCulture = culture;

  //run my parsing method here

  //ensure that culture wasn't overridden:
  Assert.AreEqual(culture.Name,
   System.Threading.Thread.CurrentThread.CurrentCulture.Name);
}

And the test failed with the exact error that I expected. I could then fix it by passing in a specific culture (like "en-US") NumberFormat:

private static System.Globalization.NumberFormatInfo _formatNumber =
System.Globalization.CultureInfo.CreateSpecificCulture("en-US").NumberFormat;

//essentially fixed by this:
double x = Double.Parse("12.5", _formatNumber);

I don't normally get this error because for our ASP.Net apps I use a set of utilities that already handled this, and I didn't need to worry about international users for the  windows forms because I only make those for internal development tools. However, it's another reminder why it's nice to have logging for even simple apps.

Sunday, December 9, 2007

Becoming a credible developer

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/becoming_a_credible_developer.htm]

Every developer wants to be taken seriously. We want to be credible. But what makes someone credible?

 

I've seen a lot of devs who think that being credible means:

  • Sending out a link to a guidelines document

  • Keep referring to your  last project (that no-one else on your current team was on) as that perfect project

  • Throwing out buzzwords

  • Never admitting that you're wrong

The problem is that all of these require little effort and don't really help anyone else. They don't distinguish the speaker - anyone can send out links, bluff about a former project, or throw out buzzwords.

 

I think a much better way to see if some is credible is if they:

  • Have experience in the problem domain

  • Have work products they can point to (i.e "I made this website", "I wrote this tool")

  • Can accurately predict what will happen (not "try this", but rather" do this and you'll get that")

  • Have a reputation of being correct (including other credible people who will back them up - i.e. "good references")

  • Can point to the official source (not "I heard on some blog that...", but rather "The MSDN reference spec for C# 2.0 says ...")

  • Are willing to invest their own resources into the approach or product (i.e. dogfood it). If someone won't even put their own resources in, then they probably don't believe in the approach.

You can't easily bluff these things. For example, your prediction either comes true or it doesn't (if it's ambiguous, then it's a bad engineering prediction); you either have a concrete product you can show people, or you don't. I think every industry leader you find does these things - they have tons of experience and products, make accurate predictions, have a reputation that precedes them, they write the official source, and they dedicate their lives to their cause. Now that's being credible.

Thursday, December 6, 2007

Create an object dynamically with CreateInstance using Reflection

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/create_an_object_dynamically_with_createinstance_using_refle.htm]

You can instantiate an object dynamically using Reflection. For example, the boards in TruckWars are stored in Xml files. Each board has a list of creatures:

 

  <Creatures>
    <Creature type="TankUnit" boardPosition="1.5, 7.5" team="Hero" />
    <Creature type="PickupTruck" boardPosition="1.5, 2" team="Hero" />
    <Creature type="PushButtonStayDown" boardPosition="13.5, 1.5" />
    <Creature type="TankUnitEnemy" boardPosition="14.2, 7.5" team="Enemy1" />
  Creatures
>

 

You could use an xml reader to cycle through this, and at each creature node, dynamically create a creature object. The "trick" is to have a base type (like "CreatureBase") that all your objects inherit from. You then specify the type in the xml file, and use the CreateInstance() method to dynamically create an object (to my knowledge, this requires that the base type at least have an empty constructor):

 

        CreatureBase c = (CreatureBase)Assembly.GetExecutingAssembly().CreateInstance(CreatureNamespace + "." + strType);
        c.Position = strPositionSerializedFromXmlAttribute;
        c.Team = (Team)Enum.Parse(typeof(Team), strTeam, true);    //makes an enum
        c.Name = strName;

 

You can then serialize the xmlNode's attributes and use them to set properties on that object. Thanks to polymorphism, the object will act as the derived type (for example, it will call the derived type's overridden methods).

 

This technique is often used in enterprise architecture for extensibility. The core system creates the base class, but then you can override it and set some xml config file to use your derived type.

 

Wednesday, December 5, 2007

Deploying Silverlight Apps

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/deploying_silverlight_apps.htm]

Silverlight is awesome to deploy. As this forum thread describes, you basically just need your web server to setup the right mime type ("Extension should be .xaml and the Content Type should be application/xaml+xml". I also had ".dll --> application/x-msdownload" because some other blogs recommended it).

 

You can then just ftp the files from your silverlight project (ClientBin, files in the root folder, and any other directories), to your server.

 

After dealing with Winform, ASP.Net, and XNA deployment problems, I was floored that it just actually worked.

 

Because Silverlight runs on the client machine, it is the client's responsibility to have the right stuff installed. However, silverlight makes that really easy by rendering as a single-click download link if the client doesn't have it installed yet.

 

Also note that Silverlight is not a server technology like SQL2005 or ASP.Net. You don't even need an ASP.Net server to run Silverlight. (This is especially practical if you're a hobbyist on a budget, and your host company charges extra to asp-enable your site.).

Tuesday, December 4, 2007

Using Path.Combine instead of string concatenation

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/using_pathcombine_instead_of_string_concatenation.htm]

I often need to work with filePaths, such as getting an absolute file name given a directory and a fileName. One way for developers to do this is simply string concatenation. A lot of devs do this because it appears so simple. But the problem is that when passing in the directory as a string, you often don't know if it will end with a final slash or not - i.e "C:\Temp" or "C:\Temp\". Merely doing string concat will screw it up, you could get a non-existent file like "C:\Temp\\MyFile.txt".

 

A much better approach is to use System.IO.Path.Combine. This allows you to have a directory (with or without the slash), and the combine method if smart enough to handle it:


    public static string GetFileName(string strDir, string strName)
    {
      //return strDir + @"\" + strName;   //BAD
      return System.IO.Path.Combine(strDir, strName); //GOOD
    }

 

What's funny is that both options are just 1 line of code and take essentially the exact same amount of time to write. However, the first is very brittle, and could likely cost you tons of time tracking down an error because someone passed in a directory with or without that extra slash. The second just works.

Monday, December 3, 2007

Silverlight 1.1 global page error handler

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/silverlight_11_global_page_error_handler.htm]

When writing Silverlight TruckWars, I was trying to figure out how to create a global error handler for a page. Eventually I saw the WebApplication.Current.ApplicationUnhandledException event, which I could hook up like so:

 

    public void Page_Loaded(object o, EventArgs e)
    {
        //....
        WebApplication.Current.ApplicationUnhandledException +=
            new EventHandler
            (Current_ApplicationUnhandledException);
    }

 

    void Current_ApplicationUnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
    {
      HandleError(e);
    }

 

Note that Silverlight handles errors differently than ASP.Net. In ASP, you get the yellow error page with a detail exception message (by default). However, in Silverlight, it just stops executing your code and returns to the caller, leading to weird behavior.

Sunday, December 2, 2007

10 Rules that Age of Empires Teaches about Development

[This was originally posted at http://timstall.dotnetdevelopersjournal.com/10_rules_that_age_of_empires_teaches_about_development.htm]

I think that Age of Empires II (AoE) is arguably the best computer game of all time. A close second would be Civilization IV. I'm not much of a computer game player, so the best way for me to justify playing a game is that it's somehow educational. Smile Here are 10 rules, that Age of Empires clearly demonstrates, which also apply to software engineering:

  1. You need to invest in research: This means the occasional big investment, as well as many frequent upgrades as resources allow.  If you neglect your research, it won't matter if you have a hundred units - they'll all be obsolete and practically useless. It may even backfire because you'll still have to maintain them (via population limits or distracting your "medics" to get healed), which will prevent you from focusing on the new, important units that can actually win the game.

  2. You need the right tools: Don't even try taking down a castle with just stone-age sling shots. Often you need a combination of complementary units - i.e. you need to coordinate siege, cavalry, medics, etc... Likewise, software engineering needs the right tools - good luck with team development if you don't at least have a good source control system and a build server.

  3. Strike a problem while it's still small: Like in real life, problems grow in AoE. An enemy team gets bigger and stronger, resources dwindle, or the clock runs outs. This is why a common strategy is to rush your opponents while they're still small. (You may also be small at the moment, but you're still big enough to beat them now). In development, problems grow too - bad code propagates. Knock it out early before it comes back to bite you.

  4. Protect your home base: Don't rush out on some adventure and leave you home base defenseless. Likewise, in development, your core application is your home base, and it should be protected by a suite of automated unit tests. So if you get distracted with some other "adventure", at least your code still has some defense against bad additions that would break it.

  5. Not all units are equal: Although a foot-solder and a horse-archer are each just a single unit, one horse-archer could beat ten foot-solders. Furthermore, the horse-archer, who can shoot at range (like across a river), could accomplish things that a million foot-soldiers could never do. Likewise, in development, there are real "stars" - a star dev will produce not just more than 10x an average dev, they'll create things that an average dev won't even comprehend. Granted, sometimes a company's technical needs are simple enough that they don't need stars, but it's good to at least be aware of the discrepancy.

  6. Your plan can be limited by mental energy: You may have 50 units, but it doesn't matter if it's impossible to manage them. AoE is an arcade game, and you may make dumb decisions simply because there wasn't enough time to think out the perfect solution. I personally prefer a handful of powerful units that are easy to manage, as opposed to an army of weak units that just get plucked off because they're too hard to coordinate. Same applies to software engineering, but even more so. Most of what you do is limited by mental energy (code being hard to maintain, a purist algorithm taking too much thinking to figure out, etc...). Mental energy is as real a resource as gold.

  7. Don't rely on cheat codes: AoE has cheat codes. However,  don't depend on them because they could be disabled or make you miss the bigger picture. In software engineering, the equivalent is to use hacks - bad code that solves the immediate problem now, only to break tens times as much later.

  8. Expect the unexpected. In AoE, there are other teams actively working against you. The enemy may not attack your straight on (where all your defenses are), but may instead be creative and attack from the side, or ambush behind, or siege you, or something else. Likewise, in software engineering, there are constant business changes, miscommunications, developer bugs, new technologies, all causing unexpected things to happen.

  9. Recognize the common patterns and solutions: AoE has perhaps hundreds of units. With many different civs (each with their own unique unit), it's a lot to have a special plan for every single unit that you may encounter.  However, there are comparatively only a few categories for them - like infantry, ranged, cavalry, siege, naval units, etc... By looking at the common patterns (ranged units usually beat infantry, cavalry usually beats siege), you can abstract out the details and make a flexible plan. Same thing in software engineering - there are common design patterns and ways to abstract out complexity via domain-specific-languages. Be prepared to look at the high-level abstract issues, then drill down into the details.

  10. It should be fun: AoE is a good, old-fashioned-fun kind of computer game. It has huge replay value. Likewise, development should be fun, especially if you have the right process and schedule in place.