Wednesday, March 11, 2009

Program Life Cycle

I’m going to be boring for a minute and just talk about plain, old programming. When you write a program from scratch it is brand new and shiny. Everything works completely as it should and it feels like a brand new card.

But any good program is constantly modified in order to add new and better features, so the program grows little hairs and feels “less new.” When a program gets into middle age it is harder to add new features because everything feels fragile. Modifications that were never intended have been grafted in, so the design is a little bit messy. (MTG Forge barely supports planeswalkers.)

After awhile, pick whatever time span that you want, the program feels old. New features are very hard to add and constantly require the programmer to “tip-toe” around the old code. Parts of the program may have completely been rewritten and are poorly documented. And overall the program feels like a 10 year old car, usable but definitely not new.

After a program has gotten “old”, it is time for a bright, shiny new version. MTG Forge has probably become old and needs to be retired. That way the new 2.0 version can have a different, improved design that fixes many of the “hacks” that were needed with the old code.

I’ve started working on version 2.0 and it is a tough beast to handle. At first I was too ambitious, but at the very least it will require a total rewrite from the ground up. Many of the ideas from version 1.0 can be applied to 2.0, but the implementation will be completely different. A plausible goal is to get 2.0 working with a few cards so that you can see progress without being completely overwhelmed.

With all of that said and done, it is still nice to add new stuff to 1.0 because it works and you can immediately see the results.

9 comments:

Tim said...

Whenever programmers are given legacy code to work on, their natural instinct is to bulldoze everything to the ground and start from scratch. Now there's nothing wrong with starting from scratch - as long as you've gained experience by writing v1, and learned lessons - what pitfalls to avoid, what works, what doesn't, then great.

There is another way, that's also worth bearing in mind. It's called Refactoring. When the code starts to get old and you daren't breath in case it all comes toppling down - this is code rot - also known as code smell. But with refactoring, you take just one small piece at a time, and rewrite it to make it that little bit less smelly.

In refactoring, you try not to change any of the external functionality of the code (and risk introducing new bugs!). You are just looking to make the code easier to use from the developer's point of view.

Come across two (or more) very similar looking functions that you just know if you try adding a feature to one you're going to forget about the others? Refactor them into one function and parameterize the differences.

Found a function full of variables like "temp" "i" "j" "magic" "ok"? Rename the variables to something more meaningful.

Got a class which seems to do two (or more) completely distinct things that don’t really belong together? Refactor the class into several distinct classes.

The plan is not necessarily to fix bugs (but by all means fix bugs if you spot them while refactoring) but to make the code that much easier to work with. So hopefully after a little bit of maintenance work, you can find yourself having to tiptoe around the code just a little bit less, and not worrying as much that adding one new feature will have a house of cards effect and break ten other features! And you don’t have to do all your refactoring at once. Just chip away at the code a little bit at a time, and over time you’ll get dazzling results!

And if you do decide to bulldoze, and work on V2, don't forget to maintain the new version - ever spot smelly code in v2 - don't hesitate to refactor it and keep your code spring fresh :)

Good luck with v2 Forge - keep up the good work!

Gando the Wandering Fool said...

Words of wisdom there Tim.

Anonymous said...

Tim, refactoring to improve understandablity is a good idea in general. However the design of v1 is what is limiting it. It doesn't just need refactoring, it needs major rewrites or hacks in order to get many things to work.

I am for v2, but I really think it should use an event based programming model. Have default rules and cards which are able to override them. It seems the only way to be able to impement all of the cards and have them isolated so that they do not cause bugs in combination of other cards.

Silly Freak said...

maybe my point of view is much too theoretical, in that it's hardly possible to implement, but I don't like the idea of a RulesEngine for handling all the rules, and stripping everything out of the cards.

I don't mean to get back to the if(card in play) like structure.
what I mean is a distributed rules engine: every calculation is done by an object, and the objects are parameterized to allow ruls-changing effects

if this is done, the parts of the rules handled by one object may be very small, at least from a non-programming point of view.

examples of rules-snippets that could be implemented as a single class may be:
-finding legal targets
-in/decreasing life
-getting a card's attributes

it is to note that the rules parts are strongly connected. if a target may be any creature, the find target class must not look itself for what cards are in play, if a card is a creature and so on.

now please don't tell me to implement, i said it's very theoretical and the mass of effects possible in magic makes it hard to find the right degree of separation. should "check legal attack" imply attacking costs, or is this another module...

Forge said...

Every computer program is a balance between theory (perfection) and implementation (reality).

Currently MTG Forge has a very modular design and doesn't use a global RulesEngine object. Part of the problem is that even simple things like "is this creature an elf" gets complicated with shapeshifters and "simple" card like Giant Growth which seem to just change an integer, in fact have layers, so in MTG Forge Giant Growth doesn't work on Nightmare (power equal to the number of swamps). My idea is to make the Card object as simple as possible and to group all of the rules into RulesEngine so it can do all of the hard work. (RulesEngine will use many other classes that will be grouped by functionality like one class will handle combat, but RulesEngine will "wrap" all of those functions in case they need to be overridden, such as "white cards cannot attack this turn.")

RulesEngine is just an idea that makes sense to me and it will hopefully be an improvement over the current version. Hopefully it will let me code more cards, such as a whole set.

Generally programmers are taught that big, complicated objects are wrong, which is often true, but every design choice is trade off between the theoretical and the practical.


Silly Freak - "now please don't tell me to implement"

This is just a friendly discussion, if I didn't want comments I would turn them off, ha.

Anonymous said...

MTGForge said...

"...so in MTG Forge Giant Growth doesn't work on Nightmare (power equal to the number of swamps)."

Actually, I've fixed this in the current MTGForge, by dividing the regular attack/defense values into: baseAttack/baseDefense, temporary attack/defense boosts (giant growth) and more permanent attack/defense boosts (glorious anthem).

And it actually works :) This means in the 03-10 version a 6 swamp Nightmare with a bad moon in play will be 7/7, and it will turn into a 10/10 until end of turn if targeted by a Giant Growth.

Anonymous said...

MTGForge said...

"Generally programmers are taught that big, complicated objects are wrong"

The problem is not directly big or complicated objects, it's objects that do way too much different things (which often entails bigger objects). When objects do too much things, you have a hard time splitting, rearranging and moving code because each change affects lots of functionality. This is called separation of concerns.

This is why it's wrong that the Card class do everything related to cards. But it would be as wrong, IMHO, to have a giant object to manage all the rules. A centralized repository for the rules is good, but please split it up in simple classes :)

Classes that do a very specific thing and only that thing are
- easy to test
- easy to understand for newcomers
- easy to change (because you know it only affects that thing so it's easy to grasp the entire purpose of the object)

From my experience, a class with more than 2-3 member variables is a sign of a class that does too much.

Have a good day :)

Forge said...

I'm glad Giant Growth and Nightmare work together, go team!!!

Anonymous said...

Hello !.
You re, I guess , probably very interested to know how one can reach 2000 per day of income .
There is no need to invest much at first. You may commense earning with as small sum of money as 20-100 dollars.

AimTrust is what you haven`t ever dreamt of such a chance to become rich
AimTrust represents an offshore structure with advanced asset management technologies in production and delivery of pipes for oil and gas.

It is based in Panama with structures everywhere: In USA, Canada, Cyprus.
Do you want to become a happy investor?
That`s your choice That`s what you wish in the long run!

I`m happy and lucky, I began to take up income with the help of this company,
and I invite you to do the same. It`s all about how to choose a correct partner who uses your savings in a right way - that`s AimTrust!.
I take now up to 2G every day, and my first investment was 500 dollars only!
It`s easy to join , just click this link http://ymocofuf.bigheadhosting.net/pycavyg.html
and lucky you`re! Let`s take our chance together to become rich