Friday, May 1, 2009

PHP, Language Design, and Confusion

References

When I wrote some iterator code which returns a reference to an internal array in an object I discovered an interestingly easy to introduce bug. Without getting into the details, I left out a '&' and clobbered my underlying data structure.

Here's the offending loop:

for ($row=&$obj->firstRow();$row;$row=$obj->nextRow() {
other stuff
}

where firstRow() and nextRow() are iterator methods defined on the object which do exactly what they sound like. [oh yeah, they both are defined as returning references]

Spot the bug?

I left off the '&' between the '=' and the '$' in the update part of the for loop. This causes $obj->nextRow() to overwrite the firstRow.

Strangely enough, if I wrote this in C I wouldn't have made the error. The equivalent C code would be:

for (ptr=*obj->firstRow();ptr;*ptr=*obj->nextRow() {
*ptr-> whatever
}

Notice that I have to explicitly dereference the pointer ptr in order to clobber the initial element.

So this covers PHP and the Confusion.

Here's where the Language Design comes in:

C makes an syntactic distinction between accessing a pointer and dereferencing it.

PHP doesn't. If a variable contains a reference then assignment deferences it implicitly and silently.

Consequently, you can't really understand a chunk of PHP without reading all the definitions of the variables which preceed the chunk you're looking at. This is a bad thing and it violates that tried, true and mostly forgotten maxim of 'code locality'.

This also leads to a lot of bugs - and 'Bogus' bug reports - involving references. [just check out bugs.php.net - 655 Bogus bugs relating to 'reference']

I think PHP would be a lot easier to understand if there was a syntactic difference between assigning to a variable and assigning to the referrant of a variable.

Practically speaking, I don't think it will happen because of 'backward compatibility' and 'NIH'.

What I'd like to see is a dereference prefix which is accepted in PHP 5.x and becomes mandatory in PHP 6. There should also be a warning option which generates a warning if a variable containing a reference is assigned value and the variable is not prefixed.

Here's my first (and only) choice:
&$foo

This is currently used to denote a reference when used on the right side of an assignment. It should be a simple matter to extend this to dereferencing on the left side and in expressions.

Thursday, April 23, 2009

Efficient Code - PHP

I guess I'm a nut about execution speed. Well, not if it gets in the way of clarity, but I hate writing slow code.

But . . . always a 'But' . . . it's hard to know what is slow and what isn't. Seems like it should be easy, but I never know until I measure.

Here's an example:

The PHP manual page for preg_split() says that you shouldn't use it unless you need the flexibility of regular expressions. You should use explode() or str_split(), because they are simpler and therefore faster. I believed that.

Then I thought I'd like to see how much faster, so I wrote a couple of loops which split a comma separated string with both preg_split() and explode() to see how much I was really losing.

Here's are the average times of 10 passes through 1,000,000 splits using each:
preg_split() - 3.51239209175 seconds
explode() - 4.33661820889 seconds

preg_split() is about 23% Faster than explode().

I'm still not sure I believe it. Here's the code - so you can try it yourself.

And, please let me know if you see anything I did wrong.


$str = 'this, is , a , string, with , commas, in ,it';
$count = 1000000;
$passes = 10;
$times = array(xdebug_time_index());
$avg = array('preg' => array(), 'explode' => array(), 'map-explode' => array());
function dt($offset = 1)
{
global $times;
$last = count($times) - 1;
return $times[$last] - $times[$last - $offset];
} // end of dt()

for ($pass=0;$pass<$passes;$pass++) {
echo "Pass $pass\n";
$times[] = xdebug_time_index();
for ($i=0;$i<$count;$i++) {
$v = preg_split('/^\s*,\s*/', $str);
unset($v);
}
$times[] = xdebug_time_index();
$avg['preg'][] = dt();
echo "$count preg_split's(regx, str): " . dt() . "\n";

$times[] = xdebug_time_index();
for ($i=0;$i<$count;$i++) {
$v = array_map('trim', explode(',', $str));
unset($v);
}
$times[] = xdebug_time_index();
$avg['map-explode'][] = dt();
echo "$count array_map(trim, explode(',', str)): " . dt() ."\n";

$times[] = xdebug_time_index();
for ($i=0;$i<$count;$i++) {
$v = explode(',', $str);
unset($v);
}
$times[] = xdebug_time_index();
$avg['explode'][] = dt();
echo "$count explode(',', str): " . dt() ."\n";
}

echo "Averges\n";
foreach ($avg as $key => $ar) {
echo "$key average of $passes trials of $count splits: " . array_reduce($ar, create_function('$a,$b', 'return $a+$b;'), 0) / count($ar) . "\n";
}

Tuesday, April 21, 2009

Documentation - It's Part of the Design Process

I was documenting some code I just wrote to create a packing list for an internet store we'll be opening soon. It's kind of a hairy problem to come up with a semi-optimal allocation of goods into boxes - but absolutely necessary to do right if you want to accurately estimate shipping and keep costs down.

It took me about a week to really get my head around the problem so that I could solve it. The actual solution now looks pretty obvious - but it won't in six months or a year.

So I document all my code - profusely, but compactly.

A Strange Thing happened while I was writing the doc: I found and corrected a whole bunch of bugs - both coding bugs, 'bugs of omission', and design bugs. That's when I realized that I use the very act of documenting code as part of my design iterations.

So, I started thinking about what I do and why I do it the way I do - which is (as usual) a bit contrary to 'accepted best-practice'.

First of all, there are basically Three Types of documentation in code:
  • The Code Itself
  • Comments
  • Doc
The Code: My code is semi-self documenting. I do the usual stuff: identifiers are semantically related to their use [function save_data() - etc]; use of white space; consistent breaking of long lines and breaks at binary operators; consistent indentation.

But you can do more.

Simple, direct, and consistent code does wonders for readability. Consistency in within-loop logic [use if elseif elseif ... or switch case case default or if () { stuff; continue;} if () { stuff; continue;}]. I don't think it matters what style you use as much as consistency and simplicity.

Small is usually good as well. We used to talk about 'locality of code' meaning that our code was written in chunks which weren't 'too long.' 'Too long' usually really meant 'it fits in the window of my editor' so that you can stare at it and understand the whole thing. There's also 'too short', but the Ruby on Rails guys have the market on that with their obsessive DRYness.

Anyway, the Code itself is always the final authority on what the program does - so it's the most authoritative documentation.

Do yourself a favor and make it as readable as possible - you might have to fix it later.

Comments: Helpful hints which make the Code more readable. Some (IMHO) IDIOT came up with the idea that Comments Must be Set Off with Big Boxes of Stars or something like that.

Why?

Well, I'll tell you.

We used to write programs on Paper and then punch them into Punch Cards and run them through a Card Reader and then get a Big Wide Printout on Greenbar paper and do our debugging at a desk. We'd page through this Paper with a pencil and write revisions to our programs. The paper was 14 inches wide [132 columns] and 11 inches long [60 lines at 6 lines per inch with 1/2 inch top and bottom margins].

In those days it made sense to Mark Off the comment blocks. It made them easy to spot - both for someone reading the code and for the Boss as he walked by your desk. [You'd get demerits for not commenting your code, so it was a good idea to make it easy for him to see].

Things have changed. You can easily flip around in an on-screen editor, but you can't very well write on it. It works a lot better if the information in the editor window is very dense. The way to do that is to write meaningful comments with as little excess white space as possible.

Now I use blank lines to separate logically disjoint chunks of code and preceed them with comments - if appropriate - but I don't waste a lot of screen space blocking off comments.

I think we need a very high Signal to Noise ratio - and big blocks of comment markers are just noise.

I don't use PHPDoc because those guys seem to think that the Documentation and the Comments are More Important than the Code itself. I don't. I want as much Code on the screen as I can get if it is blocked out so that the logic is obvious and commented as needed.

The Doc: The Doc is narative description of what the code does, how it does it, how to use it, calling parameters, etc etc. Can't live without it.

I've experimented with all kinds of methods. I used to write UNIX style man pages - and they are still a really good format. But now days, everything has to be in HTML, so I've changed.

There are a lot of Documentation Project things - like PHPDoc - but I don't use them.

I think they're too complex and they are usually specific to one language. Besides, they just aren't necessary for good, clear documentation.

Like many people, I work in several languages [one reason I'm bald] so I need a multi-lingual documentation system. I also want something which doesn't take up a lot of space in my program files and which is really simple. Here are my 'Must Haves':
  • Must allow the Doc to be imbeded in the same file with the code
  • Must produce HTML
  • Must be flexible
  • Must be very Simple
I couldn't find anything which didn't take multiple days [or weeks] to learn, so I wrote up something to pull Textile makeup out of a simple text file and that's what I use.

Frankly, I'm smart enough and write well enough to describe what some code does. So are you. Just pretend you're writing for yourself about a year from now when you have to find a bug and fix it in record time - or you'll be fired.

What kind of Doc do you want then? Clear, correct, self-contained without a lot of references jumping around, etc etc. Write something which will save your butt when the crunch comes and you're fine.

Design?

So what does this have to do with Design?

As I write my Doc - part 3 of the Code Documentation - I find myself thinking about how the whole thing works and how understandable it is. It can get kind of contemplative.

Another thing which happens is that I find myself wondering why in the hell I wrote something and what it really does - so I go through the logic again.

There's a different perspective and attitude when reading code to understand and clearly document it than there is when writing it initially. At least there should be.

When I write code, I get involved in the microscopic detail, in solving the problem, in creating the logic and the loops.

When I read code to document it, I get involved in the overall strategy of the solution and the clarity of the logic. It's just different.


Anyway, that's how I think about Documenting code now days.

Oh, if you're interested in my document extractor - I'm planning on dumping on the download page of http://www.clove.com. It's written in Python, so it should run about anyplace.

Tuesday, March 3, 2009

How can you screw up XML? Part II

XML is overly wordy. It has lots of required text.

I think the theory is - if there really is one - that longer words make the computer-code more easily understood. That was certainly true when we were using FORTRAN with 6 character variable names and Basic with 2 characters - but there's a limit. That limit is probably around 32 characters - 1/3 of a screen line - at which point you need some white space and punctuation.

XML doesn't provide for white space, just punctuation. As (semi-)intelligent creatures, we need white space to clump symbols into recognizable things. It seems to be how we parse. Computers don't distinguish, so . . . They can read XML, but we can't. [maybe it's really a plot by the machines and W3 are a bunch of Cylons?]


So maybe the extra stuff is supposed to make XML more reliable? To get there, we have to talk about two slightly different subjects: Bandwidth and Information Theory.

Bandwidth measures how fast we can transmit messages. The bigger the bandwidth, the faster the electronic wiggles are and so the more Bits we can represent in a given chunk of time - say a second. Big Bandwidth is Good.

Effective Bandwidth is the fraction of the real Bandwidth you get to use for your stuff - the content you want to see, transmit, or use - read streaming video from hulu.com. The more non-Content characters in the Protocol [read XML] used to encode your 'stuff', the lower your Effective Bandwidth. [You pay for Real Bandwidth, but you Get Effective Bandwidth - it's kind of like sales tax or Net Income after Income Tax]

Information Theory studies how to transmit Information in the presence of Noise. It turns out that you can always get your message across accurately - most of the time - if you use a fancy enough code. A Code takes a simple message and adds a lot of extra bits which allow the receiver to tell if the message was messed up [received a 0 where there should have been a 1] and to reconstruct it. When you have more noise, you have to add more reconstruction bits. That makes the message longer. So Information Theory says - if you want reliable communication, you have to allocate some of your Bandwidth to these reconstruction bits - called Redundancy - so that your Effective Bandwidth is lower than your actual Bandwidth.

Now let's apply this to XML.

XML adds extra stuff to create a rigid structure for you message. This has Nothing to do with Information Theory because XML is transferred over TCP - which is a Lossless Protocol - meaning that, if the message gets there At All, it's guaranteed to be OK. There's No Noise on TCP. [The TCP protocol has already eaten up the Bandwidth required by Information Theory to get your 'stuff' to you]

The XML extra stuff is there so computer programs can easily parse the message and use it's Content once it gets there.

XML's 'extra stuff' shares the Same Bandwidth as the Content inside the XML message.

I ask you: what's more important: the Content or the 'extra stuff'?

Personally, I think the 'extra stuff' should be as small and efficient as possible so we can use as much of the Bandwidth for Content.

W3 must think that the 'extra stuff' is more important than content because they make their protocols as bulky as they can.

Don't believe me? go to www.w3.org and read some of their specs - try name spaces or the RDF spec or just about anything. All Structure with minimal space for Content.

Why do we put up with this?

How can you screw up XML?

XML is about the simplest thing around. How can you screw it up?

I'm writing some metadata-extraction-from-image code and started looking at Adobe's XMP.

XMP is written using the W3's RDF spec - [almost wrote RDF framework, but that would then be Resource Description Framework framework (RDF2), which might not be the same thing].

RDF defines a 'Framework' for writing machine parseable statements of the form:

Tim has a bike.

RDF calls:

  • Tim the subject

  • has the predicate

  • bike the object
It makes you write URI's for both the subject and predicate - and have translation syntax between real words and the URI's.

Here's an example from the RDF Primer. It says: 'http://www.example.org was created on August 16, 1999'
<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:exterms="http://www.example.org/terms/">
<rdf:Description rdf:about="http://www.example.org/index.html">
<exterms:creation-date>August 16, 1999</exterms:creation-date>
</rdf:Description>
</rdf:RDF>
The RDF is 5.9 times LONGER than the English Language Sentence. That's an increase
in text of about 83%. Or, to put it another way, a BANDWIDTH UTILIZATION of about 17%.

For What Gain?

Nothing. And it takes them 6 LONG RFC style documents to define this messs. That's 6 Long, Boring documents with much repetition and pedantic phrasing with many MUST's and SHALL's and MAY's.

But it can be parsed by a machine - if you can understand the spec well enough to write the code.

Why take something so simple and make it so incomprehensibly complex?

But - Believe it Not - I digress.

XMP is written using RDF [why re-invent the wheel when you can use somebody else's debacle and make it worse].

I'm not going to get into XMP - but at first glance it looks like they're using attributes for data, XML entities for data, and RDF nested structures for data - with NO obvious logic as to when and where these choices are made. To further mess it up, everything uses XPATH name spaces - which my XML parser translates back to URI's [which point to nothing, but are long and look cool], like a 'good parser should' - which obfuscates the already obfuscated and bulks out the fluff to content ratio admirably.

Yech!!!!!!!

Here's how I think XML intended to encapsulate a website's creation date:
<site-info site_name="www.example.com">
<creation-date>August 16, 1999</creation-date>
</site-info>
It's machine parse-able. It's (almost) human readable. It only wastes 50% of the bandwidth - as opposed to 85% using RDF.

How about JSON - where the entire spec fits on one web page:
{
"site_info": {
"site": "www.example.org",
"creation_date": "August 16, 1999"
}
}

Tuesday, February 17, 2009

PHP Input Cleaning

I've been buried in my be-all, end-all, does-everything CMS / e-commerce site.
I thought I'd come up for err (or air) and here's what happened.

Below is an object which does lazy input cleaning of GET and POST data.

You use it by creating a new object [surprise].

You access cleaned data as attributes of an object instance.

All the cleaned data is cached in a class variable, so you can either pass around
a global or create a bunch of instances and everything will work - within the same
instance of the PHP program, of course. Once the Object
is required, it acts kind of like a Singleton Pattern.

You can restrict the query sources to GET, POST or REQUEST and control how it
reacts to undefined query parameters.

The documentation is in a comment at the top in Textile.

In some ways, I think this is kind of neat, but on the other hand it's
really depressing how much effort goes into something with such a simple
objective. Seems like there should be an easier way.

Cheers,


<?php
/*
#doc-start
h1. RequestCleaner

Created by on 2009-02-14.
Copyright (c) 2009 Clove Technologies, Inc. All rights reserved.

h3. Usage

Create a new request cleaner:

$rc = new RequestCleaner(sources, use_modes, error_mode) - where:

* sources is a comma separated string OR an array with one or more of: GET, POST, REQUEST
* use_modes is the bitwise OR of
** RequestCleaner::USE_HTMLENTITIES - process each parameter with _htmlentities()_
** RequestCleaner::USE_HTMLSPECIALCHARS - process each parameter with _htmlspecialchars()_
** RequestCleaner::USE_NL2BR - process each parameter with _nl2br()_
* error_mode - determines how the beast responds to errors - such as non-existing query parameters.
Use one of:
** RequestCleaner::RETURN_NULL - returns NULL
** RequestCleaner::RETURN_FALSE - returns FALSE - can be distinguished from NULL by '===' and '!=='
** RequestCleaner::THROW_EXCEPTION - throws an exception

For example:

$rc = new RequestCleaner('POST', RequestCleaner::USE_HTMLENTITIES | RequestCleaner::USE_NL2BR,
RequestCleaner::THROW_EXCEPTION);


To get a 'cleaned' query parameter, use '$rc->parm', where _parm_ is the parameter name.
For example, if 'foo' is a POST parameter, then '$rc->foo' will return the value of 'foo'
from after running the 'cleaning' routines on it.

h3. Fine points

All Cleaned data is cached in a RequestCleaner Class Variable, as are the sources, use and error
modes. This means that a top level PHP program can set up the method of cleaning and
allowed sources and all included code which uses _any_ instance of a RequestCleaner
will use the same methods and cache.

Consequently, you can either create a RequestCleaner instance at top level OR in any included or
required file which needs to access query parameters. It doesn't matter.

Attempts to access undefined attributes generate an error - as specified by _error_mode_.

Query parameters which return arrays - as in <input type=... name="foo[]" ...> -
are turned into arrays of cleaned strings which can be processed using normal loops
and array_...() functions

#end-doc
*/

/**
* RequestCleaner(sources = NULL) - where sources defines a comma separated list of Super Globals
* for attribute values. Legal names are: POST, GET, and REQUEST
*/
class RequestCleaner
{
const ERROR_MODE_MASK = 3;
const RETURN_NULL = 1;
const RETURN_FALSE = 2;
const THROW_EXCEPTION = 3;
const NORMAL = RequestCleaner::RETURN_NULL;

const CLEANER_MODE_MASK = 0x07; // IMPORTANT: Change This as you ADD USE_modes
const USE_HTMLENTITIES = 1;
const USE_HTMLSPECIALCHARS = 2;
const USE_NL2BR = 4;
private static $use_modes = array(
RequestCleaner::USE_HTMLENTITIES => 'htmlentities()',
RequestCleaner::USE_HTMLSPECIALCHARS => 'htmlspecialchars()',
RequestCleaner::USE_NL2BR => 'nl2br()',
);
private static $error_modes = array(
RequestCleaner::RETURN_NULL => 'return NULL',
RequestCleaner::RETURN_FALSE => 'return FALSE',
RequestCleaner::THROW_EXCEPTION => 'throw Exception'
);
private static $sources = NULL;
private static $source_names = array();
private static $error_mode = NULL;
private static $use_mode = NULL;
private static $cache = array();
function __construct($sources = array('GET', 'POST'), $use_mode = RequestCleaner::USE_HTMLENTITIES,
$error_mode = RequestCleaner::RETURN_NULL)
{
if (!RequestCleaner::$sources) {
if ($sources) {
RequestCleaner::$sources = array();
if (is_string($sources)) {
$sources = preg_split("/,\s*/", trim($sources));
}
foreach ($sources as $src) {
RequestCleaner::$source_names[] = $src;
switch ($src) {
case 'POST':
RequestCleaner::$sources[] = $_POST;
break;
case 'GET':
RequestCleaner::$sources[] = $_GET;
break;
case 'REQUEST':
RequestCleaner::$sources[] = $_REQUEST;
break;
default:
throw new Exception("RequestCleaner::__construct($sources): Illegal Source: $tmp");
}
}
} else {
RequestCleaner::$sources = array($_POST, $_GET);
}
if (RequestCleaner::CLEANER_MODE_MASK & $use_mode) {
RequestCleaner::$use_mode = RequestCleaner::CLEANER_MODE_MASK & $use_mode;
}
if (RequestCleaner::ERROR_MODE_MASK & $error_mode) {
RequestCleaner::$error_mode = RequestCleaner::ERROR_MODE_MASK & $error_mode;
}
}
}

private function stringCleaner($x)
{
if (RequestCleaner::$use_mode & RequestCleaner::USE_HTMLSPECIALCHARS) {
$x = htmlspecialchars($x);
}
if (RequestCleaner::$use_mode & RequestCleaner::USE_HTMLENTITIES) {
$x = htmlentities($x);
}
if (RequestCleaner::$use_mode & RequestCleaner::USE_NL2BR) {
$x = nl2br($x);
}
return $x;
} // end of arrayHelper()

private function useModesToString()
{
$ar = array();
foreach (array(RequestCleaner::USE_HTMLENTITIES, RequestCleaner::USE_NL2BR,
RequestCleaner::USE_HTMLSPECIALCHARS) as $mode) {
if (RequestCleaner::$use_mode & $mode) {
$ar[] = RequestCleaner::$use_modes[$mode];
}
}
return implode(',', $ar);
} // end of useModeToString()

public function __toString()
{
return "RequestCleaner: examining " . implode(', ', RequestCleaner::$source_names)
. " Using " . $this->useModesToString()
. " / Exit Mode: " . RequestCleaner::$error_modes[RequestCleaner::$error_mode];
} // end of __toString()

public function __get($name)
{
if (array_key_exists($name, RequestCleaner::$cache)) {
return RequestCleaner::$cache[$name];
}
foreach (RequestCleaner::$sources as $source) {
if (array_key_exists($name, $source)) {
$val = $source[$name];
if (is_string($val)) {
return (RequestCleaner::$cache[$name] = RequestCleaner::stringCleaner($val));
} elseif (is_array($val)) {
return (RequestCleaner::$cache[$name] = array_map(array('RequestCleaner', 'stringCleaner'), $val));
}
}
}
switch (RequestCleaner::$error_mode) {
case RequestCleaner::NORMAL: return NULL;
case RequestCleaner::RETURN_FALSE: return FALSE;
case RequestCleaner::THROW_EXCEPTION:
throw new Exception("RequestCleaner::__get($name): Value Not Defined");
default:
throw new Exception("RequestCleaner::__get($name): ERROR: Value Not Defined / Illegal Error Mode");
}
return NULL;
} // end of __get()

public function __set($name, $value)
{
switch (RequestCleaner::$error_mode) {
case RequestCleaner::NORMAL: return NULL;
case RequestCleaner::RETURN_FALSE: return FALSE;
case RequestCleaner::THROW_EXCEPTION:
throw new Exception("RequestCleaner::__set($name, $value): Setting Attributes Not Allowed");
default:
throw new Exception("RequestCleaner::__set($name, $value): Setting Attributes Not Allowed / Illegal Error Mode");
}
} // end of __set()

public function __unset($name)
{
switch (RequestCleaner::$error_mode) {
case RequestCleaner::NORMAL: return NULL;
case RequestCleaner::RETURN_FALSE: return FALSE;
case RequestCleaner::THROW_EXCEPTION:
throw new Exception("RequestCleaner::__unset($name): Unsetting Attributes Not Allowed");
default:
throw new Exception("RequestCleaner::__unset($name): Unsetting Attributes Not Allowed / Illegal Error Mode");
}
} // end of __set()

public function __isset($name)
{
if (array_key_exists($name, RequestCleaner::$cache)) {
return TRUE;
}
foreach (RequestCleaner::$sources as $source) {
if (array_key_exists($name, $source)) {
return TRUE;
}
}
return FALSE;
} // end of __isset()
}

// end class definitions

?>


Why did I post this dramatic exposition of Programming Prowess?

No particular reason, just felt like it.

Sunday, August 31, 2008

Why I hate Idea Men

Ever shoot a rifle and miss the target? Well, if you haven't here's how you miss: you're aim is off by just a little bit.

The same thing happens when you're building software - or anything, for that matter.

I've spent about 40 years building systems of various kinds - mostly software implementations to 'solve' some problem. The process is pretty straightforward and goes like this:

1. Try to figure out what you're going to do.
2. Pick the most important parts and the most important actions and interactions between the parts.
3. Pretend that is all there is in the world and build some software which mimics everything you've thought of. By the way, these parts, actions and interactions are the 'model'.
4. Test it until it works well enough to do the job.
5. Knowing what you now know, go back to step 1 and do it all over again.
6. Repeat step 5.
7. Repeat step 6.

You get the idea.

Nobody is ever right the first, second, third, or even the 'last' time.

Models aren't reality - we just work on them until they are 'close enough'.

What's this got to do with Idea Men?

Idea Men do parts 1 and 2, then they get somebody else to do step 3.

Then they blame the guys who built the thing in step 3 because they don't want to do step 4.

Finally, they won't do step 5 because they're 'right' and the guys who built it are all incompetent and that's the reason it doesn't work like they said it would.

Then the Idea Men get promotions and raises.

Crap