Friday, June 19, 2009

Cargo Cult Engineering

Process-oriented development achieves its effectiveness through skillful planning, use of carefully defined processes, efficient use of available time, and skillfull application of software engineering best practices. - Steve McConnell
I'll come back to that quote eventually, but today's post is on cargo cults.

In my experience, engineering teams succeed because there's one or two engineers on the project that are smart, hard-working self-starters, but most importantly follow sound software engineering principles and are capable of taking stepping back and getting the big picture.

Smart, Hard-Working, Self Starters

There's ways of assessing this stuff. Personally, I think gradations of these attributes are mostly worthless. Programmers of average intelligence won't be able to tackle huge problems, or get a lot of features done, but having a smart but "unwise" (using that word as a catchall for what I'll describe in the sections below) engineer is worse than having an average-intellect but wise engineer.

Hard-working is good. But I think easy to assess. And if they don't work hard once they're in place, you need to fire them. If you can't fire them, because, say, you're in France, then that sucks. Your next job will be to get them to quit. Try transferring them to the Siberian Office.

Self Starters are handy, but again I don't think it's at the top of the list. A semi-smart, hard-working, self-starting programmer that insists on overengineering everything, following a fancy & convoluted development methodology, and is unable to assess the importance (ie context) of the parts that he is working on will build lots of great code -- that won't help your product ship or keep customers happy.

What's your goal? Are you capable of assessing context? As a manager, you want programmers that help your bottom line. That means quality code, but it also means code that you need, and code that makes your clients happy. Happy clients are more important than pretty code.

The Big Picture

Whether it's figuring out how one method fits into a class, one class into a module, one module into a project, one control into a web form, a folder or set of files into a hierarchy, one product feature into the next release, themselves into the company, their company into the industry, the product into the market, etc etc -- good engineers are capable of taking a step back and assessing context.

Bad engineers do cargo cult programming. They see the artifacts of good engineers, but they don't understand the principles behind it.

Sound Engineering Principles

Lists are popular. "7 Habit of Highly Effective People", "Top 10 Ways to Ship Better Software," the lists of core rules in methodologies, and the very frequent "Five Ways to Fit into Your Swim Suit for Summer" type stuff.

Lists are easy to make. Just observe for a little while. Pretty much anyone can make lists.

But lists aren't principles. Principles are difficult to apply. They're easy to state, but the whole trick with principles is that they must take context into consideration. Principles must also exist in a hierarchy; for each principle, there must be an antecedent principle that sets boundaries. The antecedent says why a principle is important, gives a guideline for the boundaries of the principle (where it makes sense and where it doesn't), and establishes a benchmark by which to judge the execution of a principle.

Take choosing good names for local variable. This isn't just one floating point out of hundred of practices that make for good software engineering. The list-maker will take this point and stick it into his six-page bulleted list of "Best Practices."

As a principle, one chooses good names because it aids in human parsing of code. Let's chase the antecedent principles here. Human parsing of code is important because it makes maintenance (extension and debugging) easier. Maintenance happens -- so why is it important to make it easier? Because it increases the quality of software and reduces the cost of development. Why are those things important? Why are they important on this product? The answers for quality and cost vary from project to project, and I could answer them in the abstract, but how you answer this question is what settles the boundaries of the principle.

Cargo Cults

Cargo cults mimicked the habits that they saw American military men executing. They thought that the motions themselves were what caused the airplanes to land, and the cargo to show up on the beach. Likewise, the actions that McConnell outlines in that quote above -- skillful planning, use of carefully defined processes, efficient use of available time, and skillfull application of software engineering best practices -- are habits. These are good habits, but I don't think they capture the important traits at all.

Specifically, "use of carefully defined processes" implies that browsing to some Six Sigma website and then handing down the printouts to your engineering team is sufficient for project success. That's just mimicking the habits of successful developers; it's not good engineering.

Thursday, June 18, 2009

Builder Pattern vs Factory Pattern

Versus

The builder pattern is appropriate when object creation is more complex than just calling a constructor. The factory pattern is appropriate when you have a hierarchy of created objects and you want to abstract the mapping of creation parameters to a subclass.

These patterns are often used together. Many abstract factories that I've written use builder functions. Sometimes I'll put the builder function into a base class -- which means that I have a builder function that is actually an abstract factory, which might itself use builder functions.

Now for more detail:

Builder

A Builder encapsulates complex creation into a single method (or class). If creating an object is more complex than just calling the constructor, then all of the work that goes into creating the object can be moved into one method, and that method is the Builder. 'Builder' implies only one type of created object, but that is not necessarily so. Builder really just means encapsulating complex construction!

Say that you want a Widget object, and that creating one means making a DB query or loading something from disk, constructing the object (passing in the query results), then making a few more calls to set up the object before it can be used.

Instead of copying and pasting that creation code -- query, construction, setup -- every time you need to create a Widget, you move all of that crap into a single function. The Widget constructor probably takes a whole bunch of parameters; maybe those come from the database query. Maybe the Widget uses multi-phase construction. The Builder pattern helps hide all that.

If the setup and configuration isn't really part of the created class, ie if it doesn't make sense for that class to know about all the other crap that needs to be done, the builder function might go somewhere else. I think 9 times out of 10 my builder functions are static methods in the created class itself. Instead of calling the constructor I call the builder function, and probably make the constructor private.

I usually use builder functions, not builder classes. The separate functions in a builder class might each return the same object just with different configurations, or each function might return different subclasses.

In languages where multi-phase construction is the norm (instantiate an object then make a bunch of function calls to fill it out), refactoring the construction steps into its own method is an instance of the Builder pattern!

Factory

A factory can create several different types of objects, but it returns its objects via an interface (or base class) reference. Whereas a Builder encapsulates complex construction steps, a Factory encapsulates the decision-making that figures out which specific subclass to instantiate.

Factories are accessed through a single method; that's really the point. You call one function, and it creates either a Subclass1 or a Subclass2, returning it via IBaseClass.

The Gang of Four book (Design Patterns) names both a Factory Method pattern and an Abstract Factory pattern.

In the Factory Method pattern, the factory function is virtual, and different subclasses of the creating class return different subclasses of the created class. That is, you call one class (the factory) to instantiate the second class (the created). The factory class is actually a tree - base class and subclasses. You'll have something like:
virtual ICreated* IBaseFactory::Create(...params...)
So you have two class trees: the factories and the created objects. The two trees might be parallel, ie CFordFactory::CreateSedan returns an instance of CTaurus, and CNissanFactory returns CMaxima, etc etc. Or, the two trees might be disjoint: CFryingPan and CMicrowave return an instance of CFood, while CBlender returns an instance of CDrink (where both presumably derive from IConsumable).

You'll most likely use the Factory Method pattern when you have one hierarchy (the created objects), you're about to instantiate a whole bunch of objects, and you don't want to do a switch or if/else/else trees. So you move the creation into a new class tree, instantiate the factory subclass you want, and then use a method to create your objects.

Alternately, you might have a class that creates a bunch of objects, but that class is part of a hierarchy. Depending on which specific subclass you have, you'd get different created objects. That's the Factory Method pattern.

In the Abstract Factory pattern, you've actually got a set of factory methods. A food factory method, one for drinks, one for dishware, etc. More realistically, you might have a factory method for buttons, one for checkboxes, etc, where different factory subclasses create different appearances. In a game, you might have a barracks that will create an infantry, cavalry, and ranged unit, with different factories for each player race. Instead of disjoint classes for each type of unit, one class (IBarracks) will have three methods to create IInfantry, ICavalry, and IRanged units.

Besides subclassing, your abstract factory could be run off of some other logic. The factory function could do some magic to figure out which subclass to create. It could switch off of a parameter:
ICreated MyAbstractFactory.Create( Enum paramEnum )
or it could use static data or other state to decide:
ICreated MyAbstractFactory.Create()
Abstract Factory is a funky pattern. To use it, you'll want to be creating a matched set of objects. If you find yourself wanting to do that, Abstract Factory is your pattern.

see also
Bridge Pattern vs Strategy Pattern
Ownership, Aggregation, and Composition

Wednesday, June 17, 2009

Designing an MMO part 1

Designing a New MMO, Part I: Get Rid of Classes!

Everyone wants to make an MMO. They're fun games, and with WoW pulling in over a billion dollars a year it looks like an insanely lucrative market.

Except, of course, for all those failures. Like Tabula Rasa, which cost a hundred million to make and only brought in a sixth of that in revenue. Unless you've got 84 million dollars you don't mind never seeing again, jumping in should be done with care.

I like thinking about MMO design. I think it's like talking politics. It's not like me and the rest of the crew at the water cooler are going to run for office. Ultimately, the only effect each of us has is one vote -- out of millions. Does it really matter what I think about politics? At most, I'm influencing a dozen people. And I haven't yet converted any of them to the One True and Proper Political Party, so what does it matter?

It doesn't matter. It's fun, though. Likewise, us scrubs talk MMO design. It's an entertaining exercise.

Usually the first topic to come up is something like "classes are lame" or "levels are boring". I think this is a fairly fundamental discussion.

But I'll skip it, because Lum did a much better job than me. Go read that.

(There's no guarantee that I'll ever write a part 2.)

Thursday, June 11, 2009

Reusing End-Game Content

I read a post on end-game content over at Player Vs Developer and was reminded of a suggestion I made to Blizzard long ago.

There are basically three problems that I'll address in this post: players want new content, they want a variety of content, and devolpers don't want to throw away old effort or see great content go unused.

Quake and CTF

One issue I had with Warsong Gulch (a 10-vs-10 Capture the Flag PvP zone) was that the map got boring. This is one issue with FPS games -- many players like having different maps.

Back when I played Quake, there were a handful of maps that everyone played on, and it was interesting to continue playing on the same map, over and over. I was actually learning new things about the maps after a year of play; specifically, I was learning player behavior. Learning where things are on the map is the first step; then one develops patterns; then one learns what patterns the enemy has; and then a metagame starts where players start trying to deceive their foe about what pattern they are running, etc etc. In Quake, I was learning very specific timing patterns, and how to juke out other players and make them think I was somewhere else. I was counting on my opponent knowing the map so well that I could play against that knowledge.

This is like tennis, or basketball, etc. Everyone plays tennis on the same court, don't they get bored of the same layout game after game? The answer is obviously no; the game isn't about the court, the game is about the other player.

Not so with online games like Quake or Warcraft because most players (especially new or casual players) don't want to become PvP pros. And many other players resent having to learn a map well, and instead just want to win without putting in effort. Don't underestimate your players' arrogance. Many players that suck don't believe it; they blame their losses on bad map balance, or the fact that their opponent knows the map 'too well', or some other lame excuse. Players suck. People suck.

When I played Quake on the LAN at work, I would learn the maps quickly (I'm good like that), or I'd remember the map from online play. I'd grab the rocket launcher and red armor and then start tearing people up -- in part because I also played a lot and was a good player. They'd get frustrated or bored, because to them the interesting bit was the new map, not the game mechanics themselves. They wanted a slot machine, where sometimes they won. They wanted to win; they didn't want to earn the win.

My point is that a great majority of people that will pay for your product want variety, not challenge. Don't force them to play competetive tennis; they want wacky new rules and a roll of the dice.

So am I now just bitching about WSG because I want to see new maps? Not exactly. Quake was played on a handful of maps; WSG only takes place on one map. Every time you want to play Capture-the-Flag (CTF) in WoW, you have to go to WSG. The other PvP maps have different gameplay -- Arathi Basin and Eye of the Storm both have a Battlefield/Team Fortress-like base capture mechanic; Alterac Valley is a back-and-forth push to the enemy's base.

My PvP Suggestion

My suggestion to Blizzard was to make more CTF maps, then change the queue mechanism to be somewhat like Arena, so that when someone queued for "WSG", they'd really be queueing for CTF, and sometimes they'd play in Warsong Gulch, sometimes in Netherstorm Gulch, sometimes in Grizzly Gulch, etc.

The major problem with just adding those as separate queues is that it's hard to find players. Now, even with queues spread across an entire battlegroup, sometimes it's hard to find people to play in Alterac Valley. Imagine if there were three times as many PvP queues -- some of those games would never get started! Hence: group several WSG-type maps into one queue. You get more players funnelling into the same queue, and players get to experience a wider variety in online maps. (This is why most Counter-Strike and Team Fortress servers rotate through maps!)

Players Want More Content.

This issue with finding players is also a problem (now that a new expansion has come out) for old end-game content. Who wants to run Scholomance or Kharazan? Those instances are lame! There's level 80 content to do! As much as players want variety, they don't want to do irrelevant content.

Scholomance is old. It takes too long to do all those quests. Once you hit level 61, the content starts becoming trivial, and the rewards for the grind too small. The problem is the same for level 70 instances -- it was hard then to find a group that wanted to do Mechanar, or Arcatraz, or Botanica. There were too many places to go for there to be many people that want to do one specific instance.

One way to fix this is to rebalance Scholomance so that level 80s can do it. They did that with Naxx; it's a fun challenge for 80s and the rewards are appropriate. Yet if they did this to every 60 and 70 instance, it'd be a pain to find a group to do anything. It'd be the problem with Mechanar but far bigger. Especially with the way itemization works -- one person wants to get his hat from here, the next guy needs a pair of pants that drops off a boss there, and once they got their drops they'd never want to do the instance again. It'd be nearly impossible to find someone to do any one specific instance, just because there'd be so few people that want anything that drops from there!

One way to solve that is the token system used at the end of the 60 lifecycle and was fairly widespread in the Burning Crusade world: kill a boss, get a token that can be used by a handful of classes for a number of different armor slots.

Now imagine if you needed Keeper of Time rep for some level 80 gear that you could only get from the Keepers, but that you could get the rep from any of a half-dozen old instances (rebalanced for level 80) and that it also didn't matter at all which one you did. Now you could say "I want to do one of the Caverns of Time", and anyone that wanted Keeper rep could do it. It used to be that people wanted (say) Durnholde specifically because that's where their item dropped. What if their item dropped from all of those instances instead of just one boss in just one instance?

Now everyone could do Caverns of Time again. The developers could re-use end-game content, and players would have a wider variety of options for where to go. The developers could add in one or two new CoT instances, and maybe redo one of the old ones, and everyone (new and old players alike, ancient characters and brand new alts) would have a much wider variety of content to choose from. A group of five players could choose which instance they enjoy rather than which instance that itemization forces them to pick. Players would be far more likely to be able to play a new instance, instead of feeling forced to go do the same instance over again.

The downside to this, of course, is that maybe players are bored of the Caverns -- especially those that were playing before BC came out and have been playing since. I have a hard time believing that Bliz couldn't just redo each of those levels. Seriously. They're making billions of dollars a year on the game. And they could reset KoT rep to Revered, or maybe add something past Exalted, or add a new faction that automatically becomes Revered 0/21000 if the were Exalted before, etc etc, so players would have a reason to go back.

Players want new content. Players want varied content. Developers don't want to develop content, and then effectively throw it away because no-one is doing it any more.

The easiest thing to fix, really, is throwing away content. They removed Old Naxx from the game. They could remove Old Scholomance and who would know or care? Spending the money to develop New Scholomance would be trivial to them, it would be new content even to old players, and (with sufficient itemization eg through tokens) would give players a broader set of dungeons to explore, instead of hitting Kara week after week after week after week after zzzz....