Wednesday, August 10, 2011
Stages of Competency
Wednesday, October 20, 2010
Switchpy
- 2.5.4, because that's the earliest version we support at work for some internal tools
- 2.6.6, because one particular internal tool jumped the gun and started using the "with" statement before we migrated to...
- 2.7, because that's what we're migrating those internal tools to (slowly)
- 3.1.2, because that's what we're targeting for new development
- A "special" 3.1.2, which mimics the version we've modified for use in our embedded devices
- The most recent 3.2 alpha, for testing
- A 3.2 trunk install, for testing patches
- Supports Windows Python versions from 2.5 up to 3.2
- Changes the local PATH environment in the current shell (via the same batchfile trick as mpath)
- Updates the Registry-based associations (via code from Ned's script)
- Pings Explorer so that if you run "python.exe" from the Start | Run command, it notices the update
- Automatically reads installed official versions from the Registry, so you can say "switchpy 31" instead of "switchpy c:\python31"
C:\src\myscript>switchpy 25
Switching to Python at C:\Python25\...
Python is now C:\Python25\
C:\src\myscript>py.test
============================= test session starts =============================
python: platform win32 -- Python 2.5.4 -- pytest-1.3.0
test object 1: C:\src\myscript
myscript\tests\test_script.py ...
========================== 3 passed in 0.03 seconds ===========================
C:\src\myscript>switchpy 31
Switching to Python at C:\Python31\...
Python is now C:\Python31\
C:\src\myscript>py.test
============================= test session starts =============================
platform win32 -- Python 3.1.2 -- pytest-1.3.1
test object 1: C:\src\myscript
myscript\tests\test_script.py ...
========================== 3 passed in 0.03 seconds ===========================
Monday, May 10, 2010
Language of the Year
Tuesday, February 16, 2010
Mpath: command-line path manipulation for Windows
I'm a command line geek. Windows' style of installing everything in its own directory makes it easier to clean up after uninstallation, but it makes for very long PATH environment variables. If I put every directory containing command line tools in the system path, it gets too long for Windows to handle. So I usually end up doing "PATH=%PATH%;c:\somethingelse\bin" just before I use it. That also makes for long paths over long runtimes, especially when you use it in batch files (since you end up with PATH=c:\somethingelse\bin;c:\somethingelse\bin;c:\somethingelse\bin;[rest of path] after multiple invocations).
So I wrote mpath. Mpath is a combination batch file and Python script that takes advantage of some quirks of the Windows command shell, to let a child process alter the environment of a parent command shell process (something that you typically can't do in win32, but mpath gets around it by creating a temporary batch file that gets executed in the parent process).
Syntax:
-
mpath pathname :prepends pathname to the current command shell's PATH, if it doesn't already exist. -
mpath + pathname :appends pathname to the current command shell's PATH, if it doesn't already exist. -
mpath - pathname :removes pathname from the current command shell's PATH, if it exists.
A quick demo:
C:\> PATH
PATH=C:
C:\> mpath c:\foo (prepend c:\foo to the path)
C:\> PATH
PATH=c:\foo;C:
C:\> mpath - C:\FOO (take it off the path--note case insensitivity)
C:\> PATH
PATH=C:
C:\> mpath + c:\foo (append c:\foo to the path)
C:\> PATH
PATH=C:\;c:\foo
C:\> mpath c:\foo (try to prepend it again--mpath knows it's already there)
c:\foo already in path.
C:\> PATH=%PATH%;c:\foo (silly user should have used mpath...)
C:\> PATH
PATH=C:\;c:\foo;c:\foo (now there are two copies!)
C:\> mpath - C:\FOO (but mpath takes care of that.)
C:\> PATH
PATH=C:
I've tested Mpath with Windows XP running Python 2.5 and 2.6. I know it doesn't work on 3.x; I plan on fixing that at some point when I need it.
Update: mpath is now tested on 2.5, 2.6, 2.7, and 3.1.
Monday, December 07, 2009
Five Pycon 2010 Talks I Need to See
1. Import this, that, and the other thing: custom importers (Brett Cannon)
This is an easy choice, because I'm about to be implementing one of these for work. Would have been be nicer if Pycon 2010 had been scheduled for September 2009, but I'll take what I can get.
2. Understanding the Python GIL (David Beazley)
Another easy choice. After reading lots of code and debugging thread issues in our embedded Python interpreter at work, I think have a decent grasp of the GIL implementation. Given David's mindbending generators tutorial last year and his GIL presentation from ChiPy, I expect this talk to be rich in things I will be disturbed to have learned.
3. Powerful Pythonic Patterns (Alex Martelli)
Alex's talk last year, Abstractions as Leverage, was curiously satisfying. He didn't present any facts I hadn't already heard or read, but his presentation made some new connections for me (in a "My God, it's full of stars!" way).
4. Threading is Not a Model (Joe Gregorio)
In the last few years, I've begun to see pervasive threading as a placebo more than a solution. To paraphrase JWZ, some people, when confronted with a problem, think, "I know, I'll spin up a new thread." Now they have two problems. In reality, they've usually created an unknown number of problems, bounded only at the lower end by the number two. I'm really interested in seeing what Joe brings to the discussion beyond the usual "threads, select(), or fork()" question.
5. Turtles All The Way Down: Demystifying Deferreds, Decorators, and Declarations (Glyf Lefkowitz)
I have a long history of utter contempt for the practice of using syntactic sugar to "re-define the language in order to provide a more concise, natural style" for a given purpose. Glyf says he "will try to convince you that all of this wonderful magic isn't all that weird". Sounds like a challenge. If you're not continually questioning your own biases, you're heading for a mental rut, so I'm going to try to attend this with an open mind (and probably leave with a thoroughly-bitten tongue).
These are the ones I will move heaven, earth, and lunch plans to see. The others I really want to attend are:
- How Are Large Applications Embedding Python? (Peter Shinners). Totally relevant for work, but probably more elementary than I'd want.
- What Every Developer Should Know About Database Scalability (Jonathan Ellis). Totally irrelevant for my current work, but I've had to work in this area in the past, so it's somewhat interesting, and I'm curious about what's changed lately.
- Optimizations and Micro-Optimizations in CPython (Larry Hastings). Pure geeky personal interest.
- New *and* Improved: Coming changes to unittest, the standard library test framework (Michael Foord). I'm not quite a test-driven development zealot, but I'm about as close as you can get without applying for membership.
- Python Metaprogramming (Nicolas Lara). More pure geeky goodness.
- Eventlet: Asynchronous I/O with a Synchronous Interface (Donovan Preston). I can't quite decide whether this is applicable to work or not, and there's only one way to find out.
- Seattle: A Python-based Platform for Easy Development and Deployment of Networked Systems and Applications (Ivan Beschastnikh). I was quite disappointed by last year's sandboxing talk (the description didn't really let on that it was all about PyPy), so I'm hoping I can pick up more from this one.
- Tests and Testability (Ned Batchelder). Probably more elementary-level than I'd like, but might have some good discussion.
- On the Subject of Source Code (Ian Bicking). Another blue-sky talk by Ian? Yes, please.
- Python's Dusty Corners (Jack Diederich). I have a feeling this will be like Doug Hellman's PyModule of The Week: 80% of it is "yeah, yeah, I knew that," and 20% is "oh, wow, how did I not know that?"
Saturday, October 17, 2009
Five Things I Hate About C++
Peter Siebel's recent post about the opinions of the folks he interviewed for Coders at Work made me remember it again. While I often reach for Python as the top tool in my toolbox these days, I've been writing C++ for most of my career, so I thought I'd take a crack at C++ first.
So:
5. No consistent ABI
C++ doesn't define a standard application binary interface (a standard for how the binaries produced from source code are laid out or linked together). If you're writing code to link against a pre-built library, then unless you're using the same version of the same compiler, you can't guarantee that your code will work correctly. (Technically, C doesn't either, but for practical purposes, though, C is in much better shape, mostly because C++'s features provide far more opportunities for implementations to disagree.)
The practical result is often that C linkage is considered "safe" and C++ linkage is considered "unsafe", which means that C linkage is the lingua franca for object-level interoperability, and no one really pushes for compatible C++ linkage--which in turn means that it doesn't happen. (While my experience is mostly in Windows programming, the situation seems to be better in the g++ and GNU/Linux world--as is often the case.)
4. Sorta-kinda safety
The first benefit of C++ over C for me wasn't object-orientation. It was that C++ seemed to be much better at catching the kinds of low-level programmer errors I tended to make back then. Class member protection, type-checked function parameters, exceptions that (unlike return codes) can't accidentally be ignored, constructors and destructors that are guaranteed to be called at the right time, improved casting operations--what's not to like?
The problem is that most of the safety features aren't really safe, they're just a little safer, often due to the desire for source code compatibility with C or concerns about run-time performance. You can probably argue that C++ is safer than C, but I believe it's "just safer enough" that C++ programmers get complacent.
Plus the interaction language of features makes it much easier to commit horrible, higher-level design mistakes that are harder to see when reading the code, particularly with things like non-trivial constructors and destructors, misused (and overused!) inheritance, and non-obvious method overrides.
3. Textual macros
The LISP world has had the "hygienic vs unhygienic macros" argument for a long time. In a (grossly oversimplified) nutshell, hygienic macros allow you to define new, reusable bits of language without worrying about the context in which they'll be evaluated. This makes for safer macro definitions, but precludes some very useful techniques that unhygienic macros allow--for example, enabling the code in the expanded macro use and affect variables in the context in which it's expanded.
But the C/C++ macro implementation makes LISP's unhygienic macros look like an Intel cleanroom. That's because they're not even really part of the C language syntax: they're just a simple, dumb textual replacement done in a preprocessing step, before compilation even occurs.
This feature inherited from C is so error prone that C++ added features like "inline" and namespacing to try to approximate the most common use cases for C macros, so that we wouldn't have to deal with them. It still didn't want to touch preprocessor macros for fear of breaking backwards compatibility, though, so now we have the worst of both worlds: a dangerous feature implemented outside the language syntax, with some of its bits duplicated in the language syntax, and guidance that says "sorry about the mess--here's some partial replacements that don't quite cover the gamut, but that's all you get. Have a nice day."
2. Worst-of-both-worlds standardization
C was born as an in-house development language in an AT&T lab in the late 1960s, and was used in anger almost from day one (for reimplementing the UNIX operating system). By the time standardization started, the language feature set was fairly solid and well-proven, and implementators already had real-world knowledge of the features.
C++, on the other hand, didn't go through this process. While C was designed as a language for implementing operating systems (and applications), C++ was designed as a language for implementing language features. It wasn't used (as far as I know) as the backbone of a single, well-known system in the way C was, so the language was free to evolve more divergently and more slowly.
Worse, the development of the language seems to have been driven by the the design and evolution of the specification, rather than by things tried and lessons learned in implementation. In some cases, features were added to the language specification before they were even implemented, in the hopes that smart compiler vendors would figure something out.
As a result, we have features that don't work like you'd expect (like std::vector
On the other hand, while C was standardized after it had mostly stabilized, the C++ standardization process started while the language was still very much in flux. As a result, the core language is full of weirdnesses that are explainable only when you know the political situation at the time.
For example, the construction "virtual void foo() = 0;" is a pretty weird way to spell "pure virtual". In The Design and Evolution of C++, Bjarne Stroustrup reveals that the "=0" construction is there because he wanted to get pure virtual functions into the language specification, but a committee meeting was coming up soon, and he didn't think he could convince enough people to get behind adding a new "pure" keyword.
This leads right into...
1. C++ tries to be all things for all people
I think this one is the root of most of C++'s problems. C++ is and has always been a "more-is-better" language. If you like C, we'll make sure you like C++ by bending over backwards to make C code still work (except when it doesn't) and by making efficiency our top, err, one of our top-ten priorities. If you like object oriented programming, we've added classes and inheritance. Oh, multiple inheritance? Yep, we heard that works well, so we'll add it in there too. Parametric polymorphism? Multiple dispatch? Currying? Oh, hrm, we seem to have painted ourselves into a corner... but we can bodge most of that in with templates and partial template specialization. Oh, and guess what? We just figured out that you can use templates to do metaprogramming, so you get that feature for free! Free is good, right?
This results in two, mostly-correct perceptions:
1) C++ is a big grab-bag of language features, some of which are razor-sharp and don't really hang together coherently, but work great so long as you're really, really careful.
2) C++ is more-or-less better than C, so long as you stay with a "sane subset" of its features.
But what is that sane subset? That depends entirely on who you talk to, and the subset that they choose usually reveals more about their own priorities and experience than anything about the language itself.
All that being said, I still choose C++ (or my own trusted subset of it, at least) over C because of the convenience of constructors and destructors, the expressiveness of templates, and the confidence I get from RAII. I still choose it over Java because I don't need to worry about a runtime VM, because I can access platform-specific APIs and native libraries at will, and no checked-exception silliness.
But that doesn't mean I don't sigh a little every time I burn multiple days chasing down an intermittent memory leak, or that I don't steal a surreptitious glance at younger, better-looking languages with less emotional baggage from time to time.
Thursday, October 15, 2009
Buying or Building... Furniture
Like office furniture.
My current project at work is winding down, and I'm rolling over to a new one. As part of the transition, I'm moving from my old, two-person office into the new team's bullpen environment.
It might seem like a poor trade, but this team chose to trade in their fairly nice offices because they valued the higher conversational bandwidth they got in a bullpen. Yes, it's a bit noisier, but most of the noise is project-related, and results in quicker and more complete information dispersal both among developers and between developers and SQA engineers (who also share the space).
The big win for me is that it reduces the barrier to pair-programming to the cost of mumbling, "Uh... can anyone take a look at this with me?" And we still have the offices for when we need to make a phone call or do an interview.
One of the stipulations on building out the bullpen was that we had to use existing furniture. Unfortunately, while our current furniture is nice (and somewhat pricey, from what I'm told), it's optimized for a one-person or two-person office. We each get a curvy desk, a table with attached bookshelf that fits the curvy desk as an extension, and a funky rolling file cabinet. But the curvaceousness of the furniture means that it only fits well in a few prescribed configurations--none of which match a bullpen where you want to pair-program!So the current bullpen, built from curvy bits loosely jammed together, isn't big enough to hold more people. And naturally, the people who handle furniture and facilities wouldn't be terribly happy with us saying, "Oh, this expensive furniture is nice. Now would you mind finding some place in our already-filled building to store it, and buy us some additional expensive furniture just like it, but without curvy bits?"
So our manager/Scrum Master, being the pragmatist that he is, decided we should build our own. From scratch.
Actually, "scratch" in this case really means heavy, solid-core interior doors for tabletops, and prebuilt folding-table legs to hold them up. Assembly is trivial, the surfaces are generous, prefinished, and attractive, and the cost was just a fraction of what we'd have paid for non-curvy versions of our standard furniture (which keeps the facilities folks happy... or at least happier).There are, of course, some drawbacks. Making single large pairing stations means that you have to choose a single table height. In our case, it was chosen for us by the height of the prefab table legs.
However, my current programming partner suffers from an unfortunate and tragic genetic defect that caused his growth to continue far beyond normal human levels (the medical term is, I believe, "freakishly tall"). I, on the other hand, boast a full 5'3" of height, which seems far more normal to me, all things being relative.So our alternative solution was to just tear the bookshelves off two small tables (again, that storage problem!), and then use one for each person, moving the tables around when we need to. The works great if the tables have cool adjustable legs like ours do.
(Mine, of course, is the station on the right.)
Wednesday, September 10, 2008
How to be the most awesome Dad ever
- The locked, most secret diary of a pre-adolescent daughter (who has lost the key),
- The knowledge that all such cheap locks are the same,
- A set of cheap luggage locks with keys,
- A frantic pre-adolescent daughter in possession of #1 but not #2 or #3, and
- A flair for the dramatic, with which one discloses that one knows how to pick locks, but it's a secret handed down from master spy to master spy, therefore the work must be done behind a locked door (which neatly conceals the fact that you're rummaging around in your bedroom drawer to find #3).
Tuesday, June 03, 2008
Fun with itertools
If a program needs a continuous network connection, and that connection is lost, it should try to reconnect. On the one hand, you want to reconnect as quickly as possible; on the other hand, you don't want to keep retrying (and failing) in a tight loop. So you use a "backoff" timer: after each attempt, you wait longer (up to a maximum limit).
As a C programmer, I would implement an algorithm that resembles this Python-like pseudocode:
# After the first failure wait half a second before retrying;
# double this each time up to eight seconds.
backoff_times = [.5, 1, 2, 4, 8]
cur_backoff = None
while 1:
try:
# Try to connect
connect()
except ConnectionError:
# Failed; update the backoff counter
if cur_backoff is None:
cur_backoff = 0
else:
cur_backoff = min(len(backoff_times)-1, cur_backoff+1)
# Wait to retry
time.sleep(backoff_times[cur_backoff])
else:
# Success; reset the backoff timer
cur_backoff = None
But in Python the code to manage the current backoff timer looks out of place.
In a high level language, when the ratio of "code that says what I want" to "code that tells the language how to do what I want" gets too low, you're doing it wrong. It means that you're spending too many mental cycles on the "how," and not enough on the "what".
In this case, Python gives me a better way to tell it just "what" I want it to do: use an iterator.
import itertools
def iter_pegged(seq):
"""Return an iterator that walks the sequence, and then 'pegs' on the last item."""
return itertools.chain(seq, itertools.repeat(seq[-1]))
backoff_times = [.5, 1, 2, 4, 8]
cur_backoff = iter_pegged(backoff_times)
while 1:
try:
# Try to connect
connect()
except ConnectionError:
# Wait to retry
time.sleep(cur_backoff.next())
else:
# Success; reset the backoff timer
cur_backoff = iter_pegged(backoff_times)
Other than the definition of iter_pegged, each line of code says what only what it wants to do, not how it wants to do it.
And that's what coding in a high level language is all about, no?
Monday, June 02, 2008
Git: what he said.
My main point was that git is optimized for messy reality, as opposed to an idealized view of software development that never really happened that way, but looks better when you draw it on a white board.
A few weeks ago, Ryan Tomayko said most of what I intended to say, in The Thing About Git.
So, "what he said". In particular:
- Git means never having to say, “you should have”
git --rebase interactiveis “a bit likegit commit --amendhopped up on acid and holding a chainsaw - completely insane and quite dangerous but capable of exposing entirely new states of mind.”
Friday, April 18, 2008
That Looks About Right
Saturday, January 05, 2008
Oh, look! A startup!
Now why would a thirty-something refugee from the tech bubble with a wife, two young kids, and a mortgage give up a good salary and stable, interesting job for the stress, uncertainty, and heavier workload of a new startup?
For me, it's all about aligning outcomes with effort. In a very early stage startup, the link between "what is about to happen" and "what am I doing" is as close to direct as you can possibly get. The outcome for the company is directly tied to what you're doing and how well you're doing it: no excuses, and no wiggle room. Naturally, there are external risks (like being run over by a company so big they barely notice the bump), you see the results of your effort clearly.
Working for a BigCo, Inc. is very different. Expending effort in a large company is like pulling on a bungie cord attached to a rock. Pull moderately, and nothing happens. Pull a little more, and the the rock comes along, but it tends to wiggle around in directions you don't intend. Yank really hard, and the rock has an annoying tendency to fly up and smack you in the head.
Now replace the bungie cord with a stick attached to the weight. You pull a little, the weight moves a little. You pull a lot, the weight moves a lot. You push, and the weight moves the opposite direction. The outcome is directly tied to your effort.
To be fair, my last company wasn't quite to the "bungie cord" stage. But when a former colleague approached me about his idea for a startup, I was drawn back in again.
Of course, to (ahem) stretch the analogy, sticks do tend to break more easily, but hey: that's the risk you take.
Thursday, December 20, 2007
Monday, May 07, 2007
10 Things I Learned at MEDC
- In person, Sue Loh sounds exactly like you'd imagine from reading the CE blog.
- In person, Doug Boling sounds exactly like you'd imagine from reading his book (except his humor comes off better in person).
- Windows Mobile 6 isn't as big a deal as it first seemed (unless you happen to be a managed code developer, which I'm not).
- The Tao is worth seeing (if you can either get in on your own coolness, or else persuade a multi-billion-dollar company to slip the owner some cash).
- You never know which of your mild-mannered colleagues will turn out to be a raving, iPod-dancing, Viva-Las-Vegas-singing Elvis fan.
- Managed code (C#) actually runs on "real" embedded devices that are too small for even CE.
- No matter how awesome your hotel looks, you still need to provide running water to your guests, or they get cranky.
- When attending a conference, make sure you introduce yourself and describe your company to as many people as you can: you never know which one will suggest a very cool opportunity a few days later.
- Windows Smartphones are now outselling Blackberry devices, and 90% of them are being sold to consumers rather than businesses.
- Despite pushing most of the cool new features onto Windows Mobile first, Microsoft still isn't abandoning CE devices just yet. Thanks for that.
Wednesday, May 02, 2007
Tuesday, May 01, 2007
O Sole Mio...
No "one more thing" announcements from today's keynote, but we did get an earful of how Microsoft wants to position the Windows Mobile family: "it's not just for business anymore". Robbie Bach, the president of Microsoft's Entertainment & Devices Division, claims that Win Mobile-based smart phones are outselling Blackberry devices, and that 90% of those sales are actually to consumers (not businesses).
That sounds great, but the massive emphasis on "all things smartphone" makes me a bit nervous. Microsoft is a smart optimizer when it comes to business strategies: what happens when their CE OEMs (like us) become 2% of their developer base, and their Windows Mobile Pro/Standard OEMs are 98%?
Won't someone think of the poor headless CE devices? Just try using your fancy QVGA display in a freezer for a few hours...
Friday, April 27, 2007
Your Memex is here. Are you using it?
In 1945, the Atlantic Journal published "As We May Think", in which Vannevar Bush speculated that in the future, a machine--the "memex," or "memory extender"--would assist researchers by storing, indexing, and retrieving every piece of information they could possibly need. A user could also add his own text, images, or recordings, and could record notes and comments on the content. And it all fit within a large desk.
This was strong stuff for the time: understand that the state of the art was the Harvard Mark I: a 50-foot-long, 10,000-pound, four-function calculator that could divide at the blinding speed of four operations per minute. To put it in perspective, Bush's prediction was made when my grandparents were not yet old enough to drive a car.
Since 2002, Microsoft Research has been working on implementing MyLifeBits, their version of the memex. And after five years of effort, they now have a one-user prototype to show for their efforts. So don't expect to be shelling out for the Microsoft Memex anytime soon.
But a few weeks ago, I had a realization. I went back to the original, 60-year-old article, and read over the description of the memex again:
A memex is a device in which an individual stores all his books, records, and communications, and which is mechanized so that it may be consulted with exceeding speed and flexibility....There are more parallels, but that's a good start.
It consists of a desk, and while it can presumably be operated from a distance, it is primarily the piece of furniture at which he works....
In one end is the stored material. The matter of bulk is well taken care of by improved microfilm. ...
Most of the memex contents are purchased on microfilm ready for insertion. Books of all sorts, pictures, current periodicals, newspapers, are thus obtained and dropped into place....
All this is conventional, except for the projection forward of present-day mechanisms and gadgetry. It affords an immediate step, however, to associative indexing, the basic idea of which is a provision whereby any item may be caused at will to select immediately and automatically another....It is exactly as though the physical items had been gathered together from widely separated sources and bound together to form a new book. It is more than this, for any item can be joined into numerous trails.... And his trails do not fade.
So on the one hand, we have a research project to create a literal implementation of the memex that might exist sometime in the future, or a distributed, chaotic, mashup of individual technologies that together get about 90% of the way there today.
Any bets on which will get there first?
More importantly, what are you waiting for that isn't there yet?
Monday, April 23, 2007
Slightly less perpetually behind
Friday, September 15, 2006
TurboGears decorator madness: linkify
@jsonify.when('isinstance(obj, User)')
def jsonify_user(obj):
result = jsonify_sqlobject( obj )
del result['password']
result["groups"] = [g.group_name for g in obj.groups]
result["permissions"] = [p.permission_name for p in obj.permissions]
return resultThe first line lets the default JSONifier rules handle the object; after that, it removes the "password" field for security reasons, and then adds support for fields that the default rules can't handle (like joins). The @jsonify.when decorator handles mapping the default jsonify() function to the type-specific version, so when you want to return a User object converted to JSON, you just return "jsonify(myUser)" and you're done.
This approach can be used for other purposes. For example, in one project, I kept running across is the need to render references to objects as links to view that object. For example, say you have a app that renders the text
Last updated at 12:00 by Joe
with the template snippet:
<p>Last updated at ${thing.last_update_time} by ${thing.update_user.display_name}</p>Easy and straightforward. But if you want to link "Joe" to Joe's user profile page, then every time you want to do this, you end up writing something like:
<p>Last updated at ${thing.last_update_time} by
<a href="${'/users/%d' % thing.update_user.id}"
title="User profile for ${thing.update_user.display_name}"
${thing.update_user.display_name}
</a>
</p>Then hours later you kick yourself because you find one place out of 20 where you made a typo in this monstrosity (see if you can find the one in the example above!).
So I "borrowed" jsonify's approach and created linkify.py for the project:
import dispatch
import model
import types
from elementtree import ElementTree
# Linkify generic methods... modeled after jsonify
@dispatch.generic()
def linkify(obj):
raise NotImplementedError
@linkify.when('isinstance(obj, model.User)')
def linkify_user(user):
link = ElementTree.Element('a',
href='/user/%d' % user.id,
title='User Profile for "%s"' % user.display_name)
link.text = user.display_name
return link
Then, in your controllers.py, you can make this available to templates:
# Add linkify to tg namespace
import linkify
def provide_linkify(vars):
vars['linkify'] = linkify.linkify
turbogears.view.variable_providers.append(provide_linkify)
And now, in your template, you just write:
<p>Last updated at ${thing.last_update_time} by ${tg.linkify(thing.update_user)}</p>Much, much nicer.
Wednesday, August 02, 2006
Yes, games ARE different.
When will Duke Nukem Forever be released? When it's done. DNF is the most popular whipping boy for a game development schedule gone horribly wrong, but there have been plenty of similar examples (including the original Unreal). Game developers are notorious for scope creep and gold-plating, as well as massive mid-stream changes in design and architecture. Why is that?
I've become convinced it's not solely due to "unprofessionalism" or lack of effective project management. You see, if you're creating a business application, or a device driver, or control software, you can specify the requirements, from which you can create a design, from which you can create code, which you can trace back to requirements. It's not hard to write effective, measurable requirements for this type of software, which means it's not hard to justify or drop a given feature with objectivity.
But games have a damnable, hidden, unwritten Requirement Zero:
Thy Game Shall Be Fun.
Requirement Zero is a real killer. Because it's not objectively measurable, it's not easy to manage. You can pick on Daikatana because of its schedule slips, or because you don't like its designer, but the bottom line was it just wasn't a fun game, and for more reasons than the obvious "sidekicks get squished by doors" bugs.
I once had a conversation with Danielle Bunten Berry (of M.U.L.E. fame) and a colleague about a particular game. It was gorgeous, free of technical glitches, pushed the envelope on technology, and had a decent storyline. But my colleague summed it up this way: "There's a big hole where the fun should be".
You see, users don't expect their word processor, or their spreadsheet, or the software that dispenses their soda at the local mini-mart to be fun. It's sufficient for it to be "fun-neutral": so long as it isn't so badly designed, defective, or poorly-performing as to be "un-fun", it's acceptable.
You can create a game that completely satisfies the spec, is totally free of bugs, finishes on-time, stays within its budget, and has a flawless marketing program--and it can still be a miserable failure if it isn't fun.
Yes, games ARE different.
