🌐
Videos Blog About Series πŸ—ΊοΈ
❓
πŸ”‘

Missionary or Mercenary? πŸ”—
1634842858  


As part of this transition to entrepreneurship, I've talked to a lot of recruiters and companies in attempts to get contracts. Of those successful, there is almost always some element of a bait-and-switch involved either with the actual duties required or payment offered versus the expectations discussed up-front.

I don't take any of it personally, sometimes it's just a negotiation tactic you have to withstand. It is nevertheless a black mark on the relationship going forward, as it's assured they'll inevitably find some way to short you subsequently. If you are good at sticking to your guns, the place they inevitably slip is being late with payment.

What baffles me is how many employees I've known throughout the years who actually accepted an offer after such an obvious bait-and-switch without holding them to initial expectations. When I talked to them about it, their rationalization of the situation was inevitably a covert contract along the lines of "they'll do it when I prove myself". This of course was an axe ground forever in secret when that never came to pass. Such poison is everywhere when you know how to look for it in most firms.

Modern employment seems built around fostering these sorts of covert contracts. This is a side-effect of managers wanting missionaries not mercenaries. The trouble is that this vision of the firm is inevitably undermined by the incompetence of their self-serving management. It can't be any other way because the phenomenon of the "single-elimination ass-kissing tournament" and it's effects described in "Moral Mazes" saturate the market in the USA totally. As such employees regularly care deeply about firms that are at best indifferent to their well being. In that case being a "missionary" feels less inspiring and more like wearing matching nikes and drinking kool-aid. Missionaries tend to get disillusioned when they realize those which they follow are not gods, just men.

Which brings us to today, where we have many firms lamenting a labor shortage. This should shock nobody paying attention to demographics. Over the last 60 years firms have enjoyed an unprecedentedly glutted labor pool thanks to both the baby boom and feminine empowerment. The reality going forward is instead a diminishing labor pool. Yet firms still regularly adjust their final offers down and withhold Hiring "bonuses" until long after the initial work. They then have the gall to wonder why they're seeing a lack of enthusiasm.

Firms will not be able to get away with the sort of chicanery which has been commonplace in the last 60 years. Both wages and the meaningfulness of jobs will have to actually adjust higher. This is bad news for the management of most firms, as they've grown fat glad-handing and hiring armies of suck-ups which make them look good without being productive.

This is ultimately a hopeful sign for the future versus our reality of the last 20 years where we have had the best engineers ever lead by the worst management. We're already seeing huge levels of attrition from firms which have clueless management. It is but a matter of time before people look around and see that we have had the answers all along, but are blind to them on a systemic level.

It would be a breath of fresh air to be able to deal with management on a professional level rather than have to engage in a guerrilla insurgency of "Fuhren unter Der Hand" to achieve the actual goals of companies. I have grown weary of having to cynically exploit middle management's impulse to look good at all costs as the way to advance in a firm rather than actually accomplishing something. That said, the army has known about the superiority of auftragstrategik, the OODA loop and ideas similar to the germ theory of management for even longer than the business community has, yet remains the poster child for the kind of management described in "moral mazes". The capacity for self-deception in managers clearly is at least as great as those who work for them.

This is perhaps the greatest reason I've left it all behind me to do this hired-gun thing. At least this way I'm not holding my breath that this entrenched set of problems gets fixed.


PTR Records: waving a dead chicken πŸ”—
1634239190  


Helping out people with their shared hosting problems inevitably runs into mail issues. One of the more common ones has to do with being put on an RBL, usually a code 550 bounce. This usually happens for one of three reasons:

  • The mailserver is configured to do what seems right and HELO per the appropriate domain rather than the domain of the server
  • The mailserver responds on an IP other than that of the domain returned by the HELO (either lack of a PTR record, or responding on the mail domain's IP)
  • The client is actually spamming
It's rarely the latter, as this is fairly straightforward to prevent with outgoing mail limits, outgoing scanning and so forth.

You'd think that this cross-referencing of a domain's A record to it's PTR would prevent a lot of spam, as this would effectively prevent spoofing. Unfortunately the reality of shared hosting, IPv4 scarcity and limited ISP tooling has crippled this. This has resulted in a reality where there are only 2 correct configurations:

  • No sharing of IPs between domains which emit mail, period. Not even CNAMEs. You can then HELO per domain on it's IP.
  • Never HELO with anything other than the server hostname, and always respond from the server's IP
As you can see allowing the latter (which all RBLs must, as it's the Lowest common denominator here) essentially cripples the cross-referencing of the sender domain's A record with it's IP and the corresponding PTR record. Spammers can spoof any domain they wish as long as their MTA HELOs correctly until they get manually reported.

So why is it that we can't share IPs and HELO per-domain/ip? It's precisely because we are doing this PTR to IP to A record cross-referencing as an automated process over at the RBL makers like spamcop et al. That, and almost nobody is ever given a /24 block of IPs (0-254 on the least significant byte of the address). Instead the ISPs usually assign smaller blocks to multiple customers. This means that they can't delegate the NS record for *.X.Y.Z.in-addr-arpa (where XYZ is the remainder of your IP, but backwards) to their client.

So instead they provide some kind of interface for their clients to add PTR records. Unfortunately many don't know that a one-to-many PTR relationship is entirely supported by DNS, much like for A records themselves. As such these interfaces inevitably only allow one domain to be specified per IP. Which leaves you with only one option: send out mail from your server's hostname and primary IP.

Meanwhile, 0 spam is prevented because of this massive hole blown in the entire scheme. The only practical outcome is that unaware sysadmins get caught up by this and are put on RBLs erroneously. Which is a pity, as if the system actually worked correctly it would be an ironclad guarantee against spoofed emails.

IPv6 could have fixed this, as we could give everyone a /64 IP range until the end of time and never run out. Delegating NS for the PTR domains could then become standard practice. IPv6 never really got adopted by the major ISPs though. Given they haven't updated their tooling for multi-PTR (which has been supported almost since the beginning of DNS), we shouldn't hold our breath that NAT is going away anytime soon either.


2 Major paths to take advantage of CDNs πŸ”—
1634143208  

🏷️ tcms

When considering a switch from traditional web hosting to something like S3 (or ipfs) plus web workers or equivalent stateless compute resources (GKP, Lambda, etc) you need to re-think how you deploy your applications. Many of the complex backends that you are used to are simply not feasible to have on the live web with this model. That said, it's not hard to write CGIs in a stateless fashion, and many only keep their state client side or in a database, which is a model that works just fine here.

That said, most of your backend probably doesn't even need to be public. It could easily be behind a firewall and just push updates to your staging and production buckets and workers. In fact, I'm considering this model for tCMS right now! There's no good reason why I need the login functionality public, at least for post editors -- all of them could run a local instance and push out updates supposing I had an rqlite backend data storage sitting in a bucket as a read only node, with the editors behind firewalls doing the RAFT thing.

From there the question is whether to do static generation or load sqlite with sql.js and do everything client-side (or a combination of both). Static versus dynamic web pages is well trod ground at this point so do what feels best for your application. Personally I will probably go with static generation as I prefer to run as little as possible on the client machine to get the best user experience.

The big drawback to using IAAS is (historically) higher TCO and a lack of good abstractions making it easy to self-host under such models. Things like OpenStack make self-hosting possible, but prohibitively expensive and it feels like swatting flies with an elephant gun for most webmasters.

Even containerization and it's orchestration tools require a lot of extra thought and code versus the traditional LAMP approach. What's really needed to swim in both pools easily are some abstractions to make it easy for you to:

  1. Treat local directories like they are S3 buckets (and/or ipfs tokens) so you have flexibility for storage deployment
  2. Treat local httpds like they're a web worker API you deploy to, rather than vhosts
Implementing such tools would effectively allow any shared host to transition their existing infrastructure into such products, albeit without automatic scaling on their end. That said, if multiple servers are supported in a sort of federated model (again, likely mediated thru rqlite), that would largely ameliorate such concerns.

For S3, s3proxy is exactly what we are looking for. As to an imitation of web workers (or other "stateless" compute resources), that would be far more complex given there is no standardization across vendors.


Dealing with out of date slave zones after an IP migration πŸ”—
1633971893  


I had a problem the other day where I did an IP migration on a server which was shipping slave zones to another server. Unfortunately, the IP migration script provided with plesk either failed to update the zone serials, or BIND9/rndc just didn't care for whatever reason. As such, the other nameserver kept returning responses with the old IPs, despite:

  1. Restarting bind, reloading rndc on both
  2. incrementing every zone serial on the zones we were authoritative for using sed
  3. Manually wasting the zonefile on the slave, then running rndc retransfer and rndc resync on the master
  4. rndc delzone of said slave, and then try retransfer/resync again
Nothing actually brought over the new zonefile. Obviously, more rigorous means of forcing the issue were required.

First, I needed to see if the zones on the slave were not updating, or if I was nuts. Unfortunately rndc showzone tells you next to nothing other than that a zone exists and where it comes from. This means I need to convert them to text, as the slave zones by default are shipped in bind's Binary format. To make a more generic alias, I've made a means to detect whether or not it's binary and "do the right thing".

showzone.source

showzone()
{
    echo ${1?Error domain name is not defined e.g. showzone test.test }
    # Update the following as appropriate to your environment, this works on plesk
    ZONE_LOC=/var/named/chroot/var

    file $ZONE_LOC/$1 | grep text &> /dev/null
    RESULT=$?
    if [ $RESULT == 0 ]
    then
        cat $ZONE_LOC/$1
    else
        /usr/sbin/named-compilezone -f raw -F text -o - $1 $ZONE_LOC/$1
    fi
}

The missing link here was doing rndc addzone in the same way it would happen automatically, to fool the system into thinking it was a brand new slave configured:

fix_borked_slaves.sh

#!/bin/bash

ZONE_LOC=/var/named/chroot/var

# Run this On slave:
for zone in $(ls $ZONE_LOC)
do
    if [ -z `rndc showzone $zone 2>/dev/null` ]
    then
        # Grab rndc's current definition
        TO_ADD=$(rndc showzone $zone 2>/dev/null | grep slave | awk '{ $1=$2=$3=""; print $0}')
        # Dump the current list for use on master
        echo "$zone" >> for_master_update.txt
        # Delete and re-add the zones
        rndc delzone $(rndc showzone $zone 2>/dev/null | grep slave | awk '{ print $2 }')
        rndc addzone $zone $TO_ADD
    fi
done

# Preamble to script you need to run on master, based on for_master_update.txt

# ADAPTER=eth0
# LOCAL_IP=$(ip addr show $ADAPTER | grep 'inet ' | awk '{print $2}' | sed -e 's/\/.*$//g')
# REMOTE_IP="REPLACEME"

# On REMOTE_IP, do a mass search/replace to make for_master_update.txt look like this and run it

#rndc -b $LOCAL_IP -s $REMOTE_IP -p 953 -y rndc-key retransfer $zone

This thorough application of the LART did the trick. I'm still not 100% sure what was BIND and rndc's problem here; bumping zone serials should always result in a transfer.

What would be better?

Nonsense like this is probably why cPanel's "DNS Clustering" never used afxrs or master/slave DNS in the first place. Eventually people liked it's enabling of a multi-master setup where you could edit the zones from anywhere in your cluster. It was of course a pretty funky system in that it had no consensus protocol whatsoever. Edit loops and Sibyl attacks were entirely possible and straightforward for customers to mistakenly produce for years because of confusing documentation. The documentation is better now, but the design of the system haven't fundamentally changed.

I was part of the team tasked with considering redesign of that system when we had to add DNSSEC support to it. Even the supermaster mode in pdns doesn't have the same strength as the RAFT protocol. As such I think the ideal setup for that is probably pdns in sqlite mode, utilizing RQlite. This has the added benefit of being simple for people to just setup themselves.


MySQL utf8 migrations: big hairy queries πŸ”—
1633018799  


I had to migrate some mysql databases to UTF8mb4 last week. You would think this all would have happened a decade ago in a sane world, but this is still surprisingly common thanks to bad defaults in packaged software. Every day there's a new latin1 db and table being made because /etc/my.cnf isn't setup right out of the box. So People google around, and find some variant of this article.

I wasn't willing to waste hours with a non-automated solution for the many tables and databases piled up over years. So, I wrote a script of course. The plan was this:

  1. Convert the DB's default encoding & collation
  2. Get the list of tables so we can iterate
  3. Try and convert the table. If that fails, it's usually due to long varchars being indexed.
  4. On failure, drop and re-add the indices on long varchars with an acceptable shorter prefix length
  5. After that, try and re-convert the table
This ended up being a 99% solution. A couple of tables had some neat problems I'll talk about at the end. So without further ado, I'll talk about the queries themselves.

Converting the DB is straightforward.

 fix_db.sh
mysql -e "ALTER DATABASE $DB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"


We'll assume for these examples that $DB is $1 (the first argument to the script).

Similarly, listing and converting tables was easy:

 fix_tables.sh ~ snippet 1
for table in $(mysql $DB -ss -e 'show tables')
    do
        mysql $DB -ss -e "ALTER TABLE $table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" &> /dev/null
        STATUS=$?
        if [ $STATUS -ne 0 ]
        then
            ...


Now we have to do the hard part of dropping and re-adding the indices, because mysql doesn't support ALTER INDEX like postgres.

This means we need to capture the index state as it currently exists and only make the needed adjustments to turn varchar indexes into prefix indices. If you didn't read the article linked at the top, this is because utf8mb4 strings are 4x as large as latin1, and mysql has a (mostly) hardcoded index member size. You can get it bigger on InnoDB for most use cases, but in this case it was MyISAM and no such option was available.

Here's how I did it:

 fix_tables.sh ~ snippet 2

    for query in $(mysql -ss -e "select CONCAT('DROP INDEX ', index_name, ' ON ', table_name) AS query FROM information_schema.statistics WHERE table_name = '$table' AND index_schema = '$DB' GROUP BY index_name HAVING group_concat(column_name) REGEXP (select GROUP_CONCAT(cols.column_name SEPARATOR '|') as pattern FROM information_schema.columns AS cols JOIN information_schema.statistics AS indices ON cols.table_schema=indices.index_schema AND cols.table_name=indices.table_name AND cols.column_name=indices.column_name where cols.table_name = '$table' and cols.table_schema = '$DB' AND data_type IN ('varchar','char') AND character_maximum_length >= 250 AND sub_part IS NULL);")
    do
        echo "mysql $DB -e '$query'"
    done

    for query in $(mysql -ss -e "select CONCAT('CREATE ', CASE non_unique WHEN 1 THEN '' ELSE 'UNIQUE ' END, 'INDEX ', index_name, ' ON ', table_name, ' (', group_concat(concat(column_name, COALESCE(CONCAT('(',sub_part,')'), CASE WHEN column_name REGEXP (select GROUP_CONCAT(cols.column_name SEPARATOR '|') as pattern FROM information_schema.columns AS cols JOIN information_schema.statistics AS indices ON cols.table_schema=indices.index_schema AND cols.table_name=indices.table_name AND cols.column_name=indices.column_name where cols.table_name = '$table' and cols.table_schema = '$DB' AND data_type IN ('varchar','char') AND character_maximum_length >= 250 AND sub_part IS NULL) THEN '($PREFIX_LENGTH)' ELSE '' END)) ORDER BY seq_in_index), ') USING ', index_type) AS query FROM information_schema.statistics WHERE table_name = '$table' AND index_schema = '$DB' GROUP BY index_name HAVING group_concat(column_name) REGEXP (select GROUP_CONCAT(cols.column_name SEPARATOR '|') as pattern FROM information_schema.columns AS cols JOIN information_schema.statistics AS indices ON cols.table_schema=indices.index_schema AND cols.table_name=indices.table_name AND cols.column_name=indices.column_name where cols.table_name = '$table' and cols.table_schema = '$DB' AND data_type IN ('varchar','char') AND character_maximum_length >= 250 AND sub_part IS NULL);")
    do
        echo "mysql $DB -e '$query'"
    done


We then execute all the mysql statements. I output this to a shell script which can then be run. If we simply ran them, we would have an issue where the indexes we are looking for have already been dropped.

Let's go over the details, as it uses a solid chunk of the important concepts in mysql. Read the comments (lines beginning with --) for explanations.

 drop_indices.sql

-- The plan is to output a DROP INDEX query
SELECT CONCAT('DROP INDEX ', index_name, ' ON ', table_name) AS query
-- information_schema.statistics is where the indices actually live
FROM information_schema.statistics
WHERE table_name = '$table'
AND index_schema = '$DB'
-- The indices themselves are stored with an entry per column indexed, so we have to group to make sure the right ones stick together.
GROUP BY index_name
-- We want to exclude any indices which dont have VARCHAR or CHAR cols, as their length is irrelevant to utf8mb4
-- The simplest way is to build a subquery grabbing these, and then scan the columns in the index via regexp
HAVING group_concat(column_name) REGEXP (
    select GROUP_CONCAT(cols.column_name SEPARATOR '|') AS pattern
    -- We need to cross-reference the cols in the index with those in the table
    -- So that we can figure out which ones are varchars/chars
    FROM information_schema.columns AS cols
    JOIN information_schema.statistics AS indices ON cols.table_schema=indices.index_schema
    AND cols.table_name=indices.table_name
    AND cols.column_name=indices.column_name where cols.table_name = '$table'
    AND cols.table_schema = '$DB'
    AND data_type IN ('varchar','char') AND character_maximum_length >= 250
    -- sub_part is the prefix length.  In practice, any ones with a sub part already set were just fine, but YMMV.
    AND sub_part IS NULL
);


Things get a little more complicated with the index add. To aid the reader I've replaced the subquery used above (it is the same below) with "$subquery".

 make_indices.sql

-- As before, we wish to build a query, but this time to add the index back properly
SELECT CONCAT(
    'CREATE ',
    -- Make sure to handle the UNIQUE indexes
    CASE non_unique WHEN 1 THEN '' ELSE 'UNIQUE ' END,
    'INDEX ',
    index_name,
    ' ON ',
    table_name,
    ' (',
    -- Build the actual column definitions, complete with the (now) prefixed indices
    group_concat(
        concat(
            column_name,
            -- COALESCE is incredibly useful in aggregate functions, as the default behavior is to drop the result in the event of a null.
            -- We can use this to make sure that never happens when sub_part IS NULL.
            -- In that case, we either print a blank string or a prefix based on what I chose to be $PREFIX_LENGTH (in my case 50 chars) when varchar/char cols are detected by the subquery.
            COALESCE(
                CONCAT('(',sub_part,')'),
                CASE WHEN column_name REGEXP ($subquery) THEN '($PREFIX_LENGTH)' ELSE '' END
            )
        )
        -- Very important to preserve the sequence in the index, these are scanned before the latter ones and can have big perf impact
        ORDER BY seq_in_index
    ),
    ') USING ',
    -- Almost always BTREE, but might be something else if we are for example using geospatials
    index_type
) AS query
-- The rest is essentially the same as the DROP statement
FROM information_schema.statistics
WHERE table_name = '$table'
AND index_schema = '$DB'
GROUP BY index_name
HAVING group_concat(column_name) REGEXP ($subquery);


As you can see, this uses nearly every trick in the book. The only improvements I could think of would be to turn the subquery into a VIEW, as it was used multiple times. Turning the create statement generator without our constraints into a VIEW is generally useful and left as an exercise for the reader.

About the only major feature I didn't use were stored procedures or extensions. This all would have been a great deal simpler had ALTER INDEX been available as in postgres. In sqlite it would be a great deal more complicated save for the fact that this isn't a problem in the first place, given utf8 is the default.

The only places this didn't work out was when tables crashed on things like SELECTs of particular rows for whatever reason. The good news was that mysqldump generally worked for these tables, and you could modify the dump's CREATE TABLE statement to setup indices properly. There were also a few places where there were very long varchars that were *not* indexed, but made the table too large. Either shortening them or turning them into LONGTEXT was the proper remedy.


What's new in playwright-perl v0.015 πŸ”—
1631664613  

🏷️ video 🏷️ testing
Quick update on what's changed since I last talked about playwright.

tCMS September 2021 update πŸ”—
1631660780  

🏷️ video 🏷️ tcms
I finally got time to do some work on tCMS! Here's the new goodies all the users of tCMS can expect going forward.

tCMS September 2021 update πŸ”—
1631660718  

🏷️ video 🏷️ tcms
I finally got time to do some work on tCMS! Here's the new goodies all the users of tCMS can expect going forward.

Games people play: Ultimate Smackdown πŸ”—
1629390195  


Mark Gardner asked me on twitter the other day why I wrote this article without specifically naming any names. I realized my reasoning here is probably worth sharing and elaborating if the question needs to be asked.

A lot of people would naturally think most of the post was about the perl community given that's sort of what this series of posts has been about up to this point. While it does indeed apply in large part, it's far from the only place I've seen these phenomena, and a general treatment will naturally be more useful. I've also personally "been the bad guy" here myself, so I can't throw the first stone.

People don't usually read blogs because they're useful. It's all about getting engagement these days which gets down to the more important reason to not "name names" unless the situation described is already well understood in the public. When you do, your brand immediately becomes pro wrestling and you're either gonna be a heel or a face.

This is also why calumny is considered sinful, as it provokes strong emotions which tend to make people do things they regret. When you decide to participate in an online kayfabe, don't be shocked when you get the wrong kind of followers. Sometimes the drooling fans are more annoying than the online lynch mob.

Beefin' is generally not what you want to see from a professional offering actual goods or services. Do you want to sell software and services, or entertainment? You generally don't get to do the same within one brand. There's a reason my political/entertainment persona online is quite separated from my professional. While I don't try to hide that it exists, the degree of separation is an important signal that you can, in fact, control yourself when it comes to what's actually important -- making an upright living through service to others.

A lot of this first year of entrepreneurship has been de-programming myself and unlearning bad corporate habits I didn't even know I had. Much of my content up to now has been little more than sharing my journey. It's been useful to some people, but nowhere near as useful as my actual skill set has been to my clients. Sure feels good to write though.


Resizing, Expanding and Using raw images πŸ”—
1629321079  

🏷️ tutorial

So you need to resize some VM images, and possibly inspect them later. Here's the dope:

 mount_loopback.sh
# Replace size with what you see to be appropriate
qemu-img resize $IMAGE_FILE +80GB
# Setup a device for the image file so that we can resize the relevant partition
losetup -f -P $IMAGE_FILE

When using LVM, you'll have to do some extra steps to get the relevant device and resize things:

 get_mapped_volname.sh
pvscan --cache
# Grab the name of the volume
lvs
# Use the name from that to plug it in, there should be something similarly named in /dev/mapper
vgchange -ay

Now you need to resize the partition:

 resize_normal.sh
# When you have a normal DOS/BSD partition table:

# Using ext fS:
resize2fs /dev/loop0p1

# Basically anything else
growpart /dev/loop0 1

Things are of course different on LVM:

 lvm_extend.sh
# Resize partition you want, usually this is device 1, but on XFS the data section is usually device 2
pvresize /dev/loop0p2
lvextend -l +100%FREE /dev/mapper/$NAME

XFS also requires special handling. You will have to mount the relevant device and then:

 xfs_grow.sh
# Or, the loop device if it's not LVM
xfs_growfs /dev/mapper/$NAME

Mounting and fiddling with stuff is as simple as:

 mount_loopbacks.sh
# Replace partition number with whatever is appropriate
mount -t $FSTYPE /dev/loop0p1 $MOUNTPOINT
# Same story with LVM stuff, but use the /dev/mapper entry
mount -t $FSTYPE /dev/mapper/$NAME $MOUNTPOINT

When done, you should remove the loopback device:

 remove_loopbacks.sh
losetup -d /dev/loop0

One last point worth noting is that if you do this while the VM is active writes will not show up until you unmount & unload the loopback devices, as they don't share journals. The best use of mounted VM disks on the hypervisor is for backups though, and for this purpose loading and mounting them just for the backup period works quite well. As such I generally also add the -r (read only) option to losetup when I'm mounting for backup purposes.


Troglodyne year 1 in review πŸ”—
1629315907  

🏷️ tcms

About halfway into my year of doing this solo entrepeneur thing, I realized a lot of my work on tCMS was not done out of a desire to outdo wordpress, ghost or any of the other CMSes which are a part of this cambrian explosion of software most of us have lived through.

Instead, it was actually done for the same reason carpenters build their own house. By god, I'm gonna do it the way I want it for once! What you get is indeed quite satisfying. Though when you zoom out and think of the long term perspective, does it actually mean as much as I feel it does? After all, generations of my ancestors built their own homes and barns. Those now living in them (if they weren't razed) now have no idea what went into it or why it was built that way.

It will be the same with software and the brands and businesses built around them. Like with houses, the only ones that will remain standing will largely be a function of what particular families, towns, firms and industries managed to stick around. As such the "0 code" grifters, for all their embarrassing obviousness are essentially right when they focus almost exclusively on building their customer pipelines.

Now that I'm out in the world of general contracting, I actually see this everywhere. The biggest and most successful businesses tend to run lean and hard on their creaking and ancient facilities be they real or virtual. Even obsolete software, hardware and real estate work just fine, and usually with great margins now that they've long outlived their depreciation curve.

What I'm trying to say here is yet another reason to not get too wrapped up in your tech. So what if it's a mountain of garbage? Plenty of money to be made mining that heap! Using a dead language? Necromancy tends to have pretty high margins! Lots of people make their livings with run-down trucks and dilapidated real estate.

Which is ultimately why I'm sticking with my little CMS. Sure it's using Perl, and probably an evolutionary dead end as far as CMSes go. But it's mine, and at the end of the day you have to live like nobody else to live like nobody else. As long as it delivers where I need it to, I'm not gonna sweat about the future. Having seen several people build successful business with worse tech up close and personal, I'm confident that I can actually build a business atop this little house for my data.

I'm quite blessed to have had good advice, prudent planning, discipline and a patient business partner which has allowed me the ability to putter around until I figured out how all this works. I'm grateful for the clients I've had up to this point and their ongoing custom. I think this next year I'll be able to finally add in a software offering of my own.

I'm also quite happy with how well I've done keeping up with my open source projects. Now that it looks like I've actually got a credible hourly rate I'm beginning to wonder if setting up a charitable OSS foundation (or getting sponsorship from something existing) so I can use this pro-bono time as a writeoff will make sense in the future. I'll have to look into this, and hopefully can get a good article and video on the subject in the future.


The reality of banality: yet more lessons from a career in QA πŸ”—
1629266857  


A great deal of the conflict in online messaging software and social networks revolves around the idea that people should conduct themselves "better" as though that were in fact possible. A thorough reading of history will make you realize that Sturgeon's Law applies equally to interactions you have with others. When have we ever not had depraved maniacs for elites, mobs of raving heretics spreading all manner of nonsense, attention seekers and every other kind of nuisance which we are currently beset by? I don't think it's ever happened for more than brief stretches of time.

Layered atop this reality is the massively perverse incentives of the "social graph". Measuring engagement is essentially a flow meter on a sewer pipe. The fact that nobody has figured out anything better than this grotesque hack to produce relevant searches is a testament to the reality of our natures and desires.

As such, the idea that codes of conduct or "Zero tolerance" policies could even begin to address deficiencies in public discourse is beyond ludicrous. This has important implications for things such as open source projects and social networks. The more they commit to openness, the more "toxic" the discussion is guaranteed to become, as this necessarily means not filtering out 90% of the possible inputs for the simple reason that they add nothing substantive.

This is why projects with BDFLs (Benevolent Dictator For Life) actually tend to work. Maintainership almost always goes hand in glove with deleting clowns from your tracker, message boards and mailing lists. On the other hand, when you have a nebulous "open" means by which authority in a project is acquired, any fool can make a run at the crown and as such will make effort to be heard.

At that point, whatever group in charge has two choices:

  • Ignore the rabble
  • Engage with the rabble until overwhelmed by sheer numbers
As such it should shock no one at all why projects run by nebulous groups of quasi-nobility tend to turn out the way they do. It is an inevitability that the group's legitimacy is questioned if for no better reason than it has a larger attack surface (again, Sturgeon's law means most of your ruling council will be of dubious quality).

On the other hand, the BDFL is indivisible, and bad ones don't get projects off the ground at all. The normal selection mechanism of the Bazaar helps us here. The attack surface is minimal, and discourse is likely to be healthy.

The question then arises, Why does the BDFL always step down? Heavy is the head that wears the crown. Scaling projects is hard. Many people use the tricks of modularization and shoving as much code into data as possible, but won't go all the way and release control, making the breaking of the monolith academic at best.

Done properly this resembles a feudatory arrangement in the classical sense. Many overlapping claims to particular subsystems, but in general one overarching BDFL for each, reporting up the stack to the ur-BDFL of the project. This is actually a pretty good description of how the linux kernel's development works right now. I hope that like the Good Emperors they choose successors wisely rather than leaving the title(s) up for grabs.

Which brings me to the point of all this. The reason this arrangement works is because it is naturally efficient to have people become experts in the systems they work on rather than generalists who know nothing about everything. Even studies back this up. So why would anyone want to mess up a good thing over little things like them being wicked sinners and conforming with Sturgeon's Law in the other 90% of their life?

Because we think things can be different, despite millenia of evidence to the contrary. Human situations change, but their nature does not. Even the idea that "we want better communities" is usually just a cope masking a lust for vengeance over personal slights.

Even in those rare moments of greatness, we have to know mean reversion is right around the corner. But take heart! In the worst of situations you can also be sure a return to mediocrity can't be far off. So rather than lament our lot, why not embrace it? This is what I mean when saying "It's better to be happy than right".

Sure you're gonna work and talk with some absolute toads. So what? Get over yourself, and throw your inner child down a well (it's OK, Lassie will eventually rescue it). Getting emotional about it never accomplished anything.

Here's the practical advice. In a technical project, you will have to deal with a mountain of BS from validation seekers. This is the ultimate motivation of both the holy crusader and the troll and neither should be tolerated unless you enjoy watching your fora transform into a river of sewage. These messages can easily be spotted because they don't actually contribute anything concrete. Reward good behavior by responding promptly to actual contributions, and leave everything else "on read".

Eventually long-time contributors (and users) get emotionally invested. This is where most of the crusaders actually come out of, not realizing they're playing a validation seeking game. Gee, what if this project I invested all this time in is actually bad? Does that mean I'm bad? Impossible! It's all these heretics...

Remain vigilant against the urge to get emotionally invested in your tools and organizations. Sturgeon's Law holds and mean reversion will eventually happen. If you can't move on, you eventually become a fool screaming at inanimate objects and attacking phantoms of your own imagining.


Games people play on P5P: Part Deux πŸ”—
1629136448  


Since I posted about the resignation of SAWYERX, even more TPF members have thrown in the towel. This doesn't shock me at all, as the responses to my prior post made it clear the vast majority of programmers out there are incapable of seeing past their emotional blinders in a way that works for them.

The latest round of resignations comes in the wake of the decision to ban MST. The Iron Law of Prohibition still holds true on the internet in the form of the Streisand Effect, so it's not shocking that this resulted in him getting more press than ever. See above Image.

I'll say it again, knowing none will listen. There's a reason hellbans exist. The only sanction that exists in an attention economy such as mailing lists, message boards and chatrooms is to cease responding to agitators. Instead P5P continues to reward bad behavior and punish good behavior as a result of their emotional need to crusade against "toxicity". Feed trolls, get trolls.

This is why I've only ever lurked P5P, as they've been lolcows as long as I've used perl. This is the norm with programmers, as being right and happy is the same thing when dealing with computers. You usually have to choose one or the other when dealing with other people, and lots of programmers have trouble with that context switch.

So why am I responding to this now? For the same reason these resignations are happening. It's not actually about the issue, but a way to get attention raise awareness about important issues. Otherwise typing out drivel like this feels like a waste of time next to all those open issues sitting on my tracker. This whole working on computers thing sure is emotionally exhausting!


Getting Features shipped in the face of resistance πŸ”—
1628695736  


The big tempest in a teapot for perl these days is whether OVID's new Class and Object system Corinna should be mainlined. A prototype implementation, Object::Pad has come out trying to implement the already fleshed out specification. Predictably, resistance to the idea has already come out of the woodwork as one should expect with any large change. Everyone out there who is invested in a different paradigm will find every possible reason to find weakness in the new idea.

In this situation, the degree to which the idea is fleshed out and articulated works against merging the change as it becomes little more than a huge attack surface. When the gatekeepers see enough things which they don't like, they simply reject the plan in toto, even if all their concerns can be addressed.

Playing your cards close to your chest and letting people "think it was their idea" when they give the sort of feedback you expect is the way to go. With every step, they are consulted and they emotionally invest in the concept bit by bit. This is in contrast to "Design by Committee" where there is no firm idea where to go in the first place, and the discussion goes off the rails due to nobody steering the ship. Nevertheless, people still invest; many absolute stinkers we deal with today are a result of this.

The point I'm trying to make here is that understanding how people emotionally invest in concepts is what actually matters for shipping software. The technical merits don't. This is what the famous Worse Is Better essay arrived at empirically without understanding this was simply a specific case of a general human phenomenon.


Hand me the MOP! πŸ”—
1628189700  

🏷️ perl

Reading Mark Gardner's latest post on what's "coming soon" regarding OO and perl has made me actually think for once about objects, which I generally try to avoid. I've posted a few times before that about the only thing I want regarding a new object model is for P5P to make up it's mind already. I didn't exactly have a concrete pain point to give me cause to say "gimme" now. Ask, and ye shall receive.

I recently had an issue come down the pipe at playwright-perl. For those of you not familiar, I designed the module for ease of maintenance. The way this was accomplished is to parse a spec document, and then build the classes dynamically using Sub::Install. The significant wrinkle here is that I chose to have the playwright server provide this specification. This means that it was more practical to simply move this class/method creation to runtime rather than in a BEGIN block. Running subprocesses in BEGIN blocks is not usually something I would consider (recovered memory of ritual abuse at the hands of perlcc).

Anyways, I have a couple of options to fix the reporter's inability to subclass/wrap the Playwright child classes:

  • Run the subprocess in BEGIN to grab the spec from the playwright_server
  • install all the Moo stuff with Sub::Install as well
  • Throw in the towel on runtime meta and rely on compile-time code generation
I could of course abandon object orientation entirely as well. The object model is familiar to users of Selenium::Remote::Driver (which is pretty much where all my user base is coming from), so that's probably not a great idea.

On the other hand, if we had a good "default mop" like Mark discusses, this would be a non-issue given we'd already get everything we want out of bless (or the successor equivalent). It made me realize that we could have our cake and eat it in this regard by just having a third argument to bless (what MOP to use).

perl being what it is though, I am sure there are people who are in "bless is bad and should go away" gang. In which case all I can ask is that whatever comes along accommodates the sort of crazed runtime shenanigans that make me enjoy using perl. In the meantime I'm going back to compile-time metaprogramming.


Dependencies: It depends πŸ”—
1627440954  


Over the years I've had discussions over the merit of various forms of dependency resolution and people tend to fall firmly into one camp or the other. I have found what is best is very much dependent on the situation; dogmatic adherence to one model or another will eventually handicap you as a programmer. Today I ran into yet another example of this in the normal course of maintaining playwright-perl.

For those of you unfamiliar, there are two major types of dependency resolution:

Dynamic Linking: Everything uses global deps unless explicitly overridden with something like LD_LIBRARY_PATH at runtime. This has security and robustness implications as it means the program author has no control over the libraries used to build their program. That said, it results in the smallest size of shipped programs and is the most efficient way to share memory among programs. Given both these resources were scarce in the past, this became the dominant paradigm over the years. As such it is still the norm when using systems programming languages (save for some OSes and utilities which prefer static linking).

Static Linking: essentially a fat-packing of all dependencies into your binary (or library). The author (actually whoever compiles it) has full control over the version of bundled dependencies. This is highly wasteful of both memory and disk space, but has the benefit of working on anything which can run its' binaries. The security concern here is baking in obsolete and vulnerable dependencies. Examples of this approach would be most vendor install mechanisms, such as npm, java jars and many others. This has become popular as of late due to it being simplest to deploy. Bootstrapping programs are almost always using this technique.

There is yet another way, and this is to "have our cake and eat it too" approach most well known in windows DLLs. You can have shared libraries which expose every version ever shipped such that applications can have the same kind of simple deploys as with static linking. The memory tradeoffs here are not terrible, as only the versions used are loaded into memory, but you pay the worst disk cost possible. Modern packaging systems get around this by simply keeping the needed versions online, to be downloaded upon demand.

Anyways, perl is a product of it's time and as such takes the Dynamic approach by default, which is to install copies of its' libraries system-wide. That said, it's kept up with the times so vendor installs are possible now with most modules which do not write their makefiles manually. Anyhow, this results in complications when you have multi-programming language projects, such as playwright-perl.

Playwright is a web automation toolkit written in node.js, and so we will have to do some level of validation to ensure our node kit is correct before the perl interface can work. This is further complicated by the fact that playwright also has other dependencies on both browsers and OS packages which must be installed.

Normally in a node project you could use webpack to fat-pack (statically link) to all your dependencies. That said, packing entire browser binaries is a bridge too far, so we either have to instruct the user properly or install everything for them. As is usual with my projects, I bit off a bit more than I can chew trying to be good to users, and made attempts to install the dependencies for them. Needless to say, I have ceased doing so.  Looking back, this willingness to please has caused more of my bugs than any other. Yet again the unix philosophy wins; do one thing, do it well. This is also a big reason why dynamic linking won -- it makes a lot of things "not your problem". Resolving dependencies and installing them are two entirely separate categories of problem, and a program only has to solve one to run.

The long-term solution here is to wait until playwright can be shipped as an OS package, as many perl libraries are nowadays. It's interesting that playwright itself made an install-deps subcommand. I hope this means that is in the cards soon, as that's most of the heavy lifting for building OS packages.


Reflections on a decade testing software πŸ”—
1626305255  


The way software testing as a job is formally described is to provide information to decisionmakers so that they can make better decisions. Testers are fundamentally adversarial as they are essentially an auditor sent by management to evaluate whether the product an internal team produces is worth buying.

Things don't usually work out this way. The job is actually quite different in practice from it's (aspirational) self-image. It turns out that the reason testers are not paid well and generally looked down upon in the industry is because of this reality. This is due primarily to the organizational realities of the modern corporation, and is reinforced by various macroeconomic and policy factors. Most of these situational realities are ultimately caused by deeply ingrained emotional needs of our species.

Testing at arm's length: wring this neck

Adversarial processes are not morally or ethically wrong. It is in fact quite useful to take an adversarial approach. For example, AI researchers have found that the only reliable process to distinguish lies from truth in a dataset is precisely through adversarial procedure. However, the usefulness of an adversarial approach is compromised when a conflict of interests exists. This is why Judges recuse themselves from trials in which they even have the appearance of outcome dependence in.

Herein lies the rub. Modern software firms tend to be a paranoid lot, as their (generally untalented and ignorant) management don't understand their software is in no way unique. They seem to act like gluing together 80% open source components is somehow innovation rather than obvious ideas with good marketing. In any case, because of this paranoia they don't want to expose their pile of "innovation" and it's associated dirty laundry to the general public via leaks and so forth. They mistakenly believe that they can secure this most reliably with direct employment of testers rather than being careful with their contractors.

This forgets that the individual employee usually has nothing whatsoever that could be meaningfully recovered in the event of such a breach, and is practically never bonded against this. On the other hand, a contracting business stakes everything on their professionalism and adherence to contract and have far more to lose than a tester paid peanuts. This lead me to the inescapable conclusion: The incentives encouraged for the vast majority of employed testers are the opposite of what is required to achieve the QA mission.

It turns out this happens for the same reason that Judges (paid by the state) don't recuse them from judgement in cases wheir their employer is the defendant or prosecutor. Because the job is not actually what it is claimed to be.

But wait! There's more conflicts of interest!

As if to rub this cognitive dissonance further in the face of the tester, modern organizations tend to break into "teams" and adopt methodologies such as scrum to tightly integrate the product lifecycle. Which means you as a tester now have to show solidarity and be a team player who builds others up instead of tearing down their work. To not do so is to risk being considered toxic.

The only way to actually do this is to prevent issues before they happen, which to be entirely fair is the cheapest point to do so. The problem of course with this is that it means in practice the programmer is basically doing Extreme Programming and riding shotgun with a tester. If the tester actually can do this without making the programmer want to strangle them, this means the tester has to understand programming quite well themselves. Which begs the question as to why they are wasting time making peanuts testing software instead of writing execrable piles of it. I've been there, and chose to write and make more every single time.

How dare you call my baby ugly: Testers are bad people

Everyone who remains a tester but not programmer is forced to wait until code is committed and pushed to begin testing. At that point it's too late from an emotional point of view; it's literally in the word -- commitment means they've emotionally invested in the code. So now the tester is the "bad guy" slipping schedule and being a cost center rather than the helper of the team. That is, unless the tester does something other than invalidate (mistaken) assumptions about the work product's fitness for purpose. Namely, they start validating the work product (and the team members personally by extension), emphasizing what passed rather than failed.

This is only the beginning of the emotional nonsense getting in the way of proper testing. Regardless of whether the customer wants "Mr. Right" or "Mr. Right Now", firms and their employees tend to have an aspirational self-image that they are making the best widget. The customer's reality is usually some variant of "yeah, it's junk but it does X I want at the price I want and nobody else does". I can count on one hand the software packages that I can say without qualification are superior to their competition, and chances are good that "you ain't it".

Neverthless, this results in a level of "Quality theatre" where a great deal more scrutiny and rigor is requested than is required for proper and prompt decisionmaking. This is not opposed by most QAs, as they don't see beyond their own corn-pone. However, this means that some things which require greater scrutiny will not recieve them, as resources are scarce.

We don't make mistakes, we R SMRRT

Aspirational self image can also stand in the way of quality when management holds mistaken assumptions, or the engineers are sold on a defective design. Many fall into the trap of "needing to be right more than to be happy" and will doggedly defend their own errors unless tricked into believing a course correction was both their idea and a perfection of their earlier impulses leading to here. Testers that do not understand this run right into a brick wall when they don't "let them have their story" and try to fit the facts into their narrative in a way that gives them a face-saving out. Ultimately the "I want to be right" thing is just a desire to be heard (read: validated) which many of us pick up competing for parental attention as children -- you are a good kid!

Many testers also fall into this validation trap, and provide details which management regards as unimportant (but that the tester considers important). This causes managers to tune out, helping no one. This gets especially pernicious when dealing with those incapable of understanding second and third order effects, especially when these may lead to material harm to customers. When time is a critical factor it can be extremely frustrating to explain such things to people. So much so that sometimes you just gotta say It's got electrolytes.

Sometimes the business model depends on the management not understanding such harms. At some point you will have to decide whether your professional dignity is more important than your corn pone, as touching such topics is a third rail I and others have been fried on. I always choose dignity for the simple reason that customers who expose themselves to such harms willingly tend to be ignorant. Stupid customers don't have any money.

It's all these emotional pitfalls that explain why testers still cling to the description of their job as providing actionable information to decisionmakers. The only way to maintain your professional dignity in an environment where you can do everything right but still have it all go wrong is to divorce yourself from outcomes. Pearl divers don't really care if they sell to swine, you know?

Business analysis sounds boring, surely we don't need a specialist

Speaking of things outside of your control and higher-order effects, there are also market realities which must guide the tester as to what decisionmakers want to know, but will never tell you to look for. Traditionally this was done by business analysts who would analyze the competitve landscape in cooperation with testers, but that sounds a bit too IBM to most people so they don't do it. As such this is yet another task which it turns out you as a tester have to do more often than not. It is because of this that I developed a keen interest in business analysis and Economics.

The Iron Law of Project Management states that you have to pick two of three from:

  • Do it fast
  • Do it cheap
  • Do it well
The reality is that in a software business with good margins (read: non-saturated market), "Do it fast" is mandatory. In startups, it has to also be cheap because your customers are less willing to go out on a limb and you have less to spend. As such it should shock nobody that the vast majority of software is riddled with bugs (which is of course good news for the tester, it's a target-rich environment).

That said the people who want to hire testers are looking to transition from fast/cheap to fast/well by throwing money at the problem. This tends to run into trouble in practice, as many firms transition into competing based on quality too early, eating the margins beyond what the market and investors will bear.

If I have to think like a businessman and a customer, why am I a salaryman?

The primary reasons for these malinvestments are interest rate suppression and corrupt units of account being the norm in developed economies. This sends false signals to management as to the desirability of investing in quality, which are then reinforced by the emotional factors mentioned earlier. Something will have to give eventually in such a situation and by and large it's been tester salaries, as with all other jobs that are amenable to outsourcing to low-cost jurisdictions. Many times firms have a bad experience outsourcing, and this is because they don't transfer product and business expertise to the testers they contract with first. There is no magic bullet there, it's gotta be sour mash to some degree. Expertise and tradition take people and time to build.

Also, price is subjective and it's discovery is in many ways a mystical experience clouded by incomplete knowledge in the first place. It should be unsurprising that prices are subjective given quality itself is an ordinal and not cardinal concept. The primary means by which price is discovered is customer evaluation of a product and its seller's qualitative attributes versus how much both parties value the payment.

This ultimately means that to be an effective tester, you have to think like the customer and the entrepeneur. Being able to see an issue from multiple perspectives is very important to writing useful reports. This is yet another reason to avoid getting "too friendly with the natives" in your software development organization.

Behold the tester, high guardian of the brand

Speaking of mystical experiences, why do we care about quality at all? Quality is an important component of a brand's prestige, which is the primary subjective evaluation criteria for products. Many times there are sub-optimal things which can be done right, but only at prohibitive costs. Only luxury brands even dare to attempt these things.

Those of us mere mortals will have to settle for magic. In the field of Carpentry there's an old adage "If you can't conceal it, reveal it. Perfect joinery of trim to things like doorframes is not actually possible because wood doesn't work that way. So instead you offset it a bit. This allows light and shadow to play off it, turning a defect into decoration.

The best way to describe this in software terms is the load bearing bug. Any time you touch things for which customer expectations have solidified, even to fix something bad, expect to actually make it worse. This is because nobody ever reads change logs, much less writes them correctly. You generally end up in situations where data loss happens because something which used to hold up the process at a pain point has now been shaved off without the mitigant being updated. Many times this just means an error now sails through totally undetected, causing damage.

These higher-order effects mean in general that "fixing it" is many times not the right answer. Like with carpentry you have to figure out a way to build atop a flawed reality to result in a better finished product. This eventually results in large inefficiencies in software ecosystems and is a necessary business reality. The only way to avoid this requires great care and cleverness with design which many times is simply not feasible in the time allotted.

Assisting in the identification of these sort of factors is a large part of "finding bugs before they happen" especially in the maintenance cycle. It's also key to the brand, as quality is really just a proxy for competence. Pushing fixes that break other things does not build such a reputation, and should not be recommended save in the situation where data loss and damage is the alternative.

The only way to actually have this competence is to not have high turnover in your test and development department. Unfortunately, the going rate for testers makes the likelihood that skilled testers stick around quite low.

Process versus Mission driven organizations: Are we clear to go to public?

I have spoken at length about the difference between Process and Mission-Driven organizations. To summarize, Mission-driven organizations tend to emphasize accomplishment over how it is achieved, while process oriented organizations do the opposite. Mission driven organizations tend to be the most effective, but can also do great evil in being sloppy with their chosen means. Process driven organizations tend to be the most stable, but also forget their purpose and do great evil by crowding out innovation. What seems to work best is process at the tactical level, but mission at the strategic and operational/logistical level.

The in-practice organizational structure of the firm largely determines whether they embrace process or mission at scales in which this is inappropriate. Generally, strong top-down authority will result in far more mission focus, while bottom-up consensus-based authority tends to result in process focus. Modern bureaucracies and public firms tend to be the latter, while private firms and startups tend to be the former. This transition from mission to process focus at the strategic and operational level is generally a coping mechanism for scaling past dunbar's number.

In the grips of this transformation is usually when "middle management" and "human resources" personell are picked up by a firm. Authority over the worker is separated from authority over product outcomes, leading to perverse incentives and unclear loyalties. As a QA engineer, it is unclear who truly has the final say (and thus should recieve your information). Furthermore, it is not clear to the management either, and jockeying for relative authority is common.

The practical outcome of both this is that it's not clear who is the wringable neck for any given project. The personell manager will generally be unininterested in the details beyond "go or no go", as to go any further might result in them taking on responsibility which is not really theirs. Meanwhile, the project manager will not have the authority to make things happen, and so you can tell them everything they should know but it will have no practical impact. As such, the critical decisionmaking loop of which QA is a critical component is broken, and accomplishing the mission is no longer possible. The situation degenerates into mere quality control (following procedure), as that's the only thing left that can be done. For more information on this subject, consider the 1988 book "Moral Mazes: The world of corporate managers".

What then, shall we do? Ultimate Smackdown!

To actually soldier on and try to make it work simply means the QA is placing responsibility for the project upon their own head with no real authority over whether it succeeds or fails. Only a lunatic with no instinct for self-preservation would continue doing this for long (ask me how I know!), and truly pathological solutions to organizational problems result from this. It's rare to see an organization recognize this and allow QA to "be the bad guy" here as a sort of kayfabe to cope with this organizational reality. IBM was one of the first to do it, and many other organizations have done it since; this is most common amongst the security testing field. The only other (pathological) thing that works looks so much like a guerrilla insurgency for quality that it inevitably disturbs the management to the point that heads roll.

Ultimately, integrating test too tightly with development is a mistake. Rather than try to shoehorn a job which must at some level be invalidational and adversarial into a team-based process, a more arms-length relationship is warranted.

Testing is actually the easy part. Making any of it matter is the hard part.


Whither Perl: Blue Collar Blues πŸ”—
1624491461  

🏷️ perl

I saw a good article come over the wire: The Perl Echo Chamber: Is perl really dying? Friends, it's worse. The perl we knew and love is already dead because the industry it grew up with is too...mature. The days of us living on the edge are over forever.

One passage in the linked article gets to half of the truth:


my conclusion is that it’s the libraries and the ecosystem that drive language use, and not the language itself.


This too was my feeling about programming languages for a long time, and why I know quite a good number of them. Hammer drive nail? Hammer good.

When was the last time you thought about making an innovative new hammer or working for a roofing company? I thought not. What I am saying is that when the industry for which a toolset is primarily associated with becomes saturated, innovation will die because at some point it's good e-damn 'nough.

The web years were a hell of a rush and much like the early oil industry, the policy was drill, baby drill. Someday you run out of productive wells and new drilling tech just isn't worth developing for a long, long time. We're here. The fact that there are more web control panels, CMSes and virtualization options than you can shake a stick at is testament to this fact.

It's not all bad news though

As they say, the cure to low prices is low prices and vice versa. Given enough time, web expertise will actually be lost, much like carpentry is in the US market (because it just doesn't pay.) The need for it won't, so what we can expect the future to look like is less "bigco makes thing" than "artisan programmer cleans out rot and keeps building standing another 20 years".

Similarly, innovation doesn't totally die, it just slows down. Hell, I didn't know you could do in-place plunge cuts using an oscillating saw growing up doing lots of carpentry, but now it's commonplace. Programming Languages, Libraries, databases...they're just another tool in the bag. I'm not gonna cry over whether it's a Makita or a DeWalt.

Get over it, we're plumbers now. Who cares if your spanner doesn't change in a century. If you want to work on the bleeding edge, learn Python and Data Science or whatever they program robots with because that's what there's demand for.


The Big Shift πŸ”—
1623865865  

🏷️ video 🏷️ troglovlog
People are wondering if the "Great Resignation" is real. I'm here to tell you that it is, and the consequences are farther reaching than you might suspect. #TheBigShift

I've already seen a number of job-hops by the most talented engineers at firms I'm in touch with here in flyover country, as they realize they can be paid better even at remote rates by joining a coastal firm.

This is a mirror of what is happening with people moving all over the country from the coasts to flyover country. The reasons for doing so are actually the same, however.

The nature of progressive taxation means that the most productive pay the most in, and as such the departure of this small minority of people has an outsized impact on tax base. The movement of these people from income tax states such as CA and NY to non income tax states such as TX and FL is having an outsized impact as such.

Similarly, the people leaving to get better remote work opportunities are those best capable of doing so; the most productive. Repeated study has shown that a minority of very productive employees do the vast majority of important work in the firm, so even a small number of high profile defections are going to have huge impact.

The question of course is "Why now?" Here, the reason for both is the same. Lockdown destroyed the inertia which was preventing movement -- the benefits of community which previously kept one from moving residence or firm were forcibly extinguished. This made all locales and firms roughly equal when it came to amenities, so you may as well just move to the highest paying and lowest taxing situation possible, as you can't exactly maximize for lifestyle anymore.

Now that people have actually pulled the trigger on movement they are realizing that those things keeping them where they were weren't that great in the first place, and actually little more than rationalizations for inertia. As such we can probably expect a de-emphasis on fringe benefits in the firm going forward.

As to the policy implications of this mass migration, it's sending an unmistakable message. Taxes are too high relative to what the citizenry of the coast get out of it, and have been for a long time. Unlike the firms, I don't expect they will adapt quickly enough to avert crisis.

Corporate CULTure: or How I learned to stop worrying and blessed the grind πŸ”—
1623278548  


A bizarre phenomenon in modern Corporations is what is generally referred to as "Playing House"; this would have been referred to in earlier times as "putting on airs". You may have noticed I've mentioned this in multiple of my earlier videos.

Generally this will mean things like Advertising how amazing your company is rather than the utility or desirability of your products. When I was young my father would point out such things to me; he generally considered it a sign of weakness in a firm, as it meant they didn't have any better ideas on what to advertise. While this is indeed true, it's not because they have no good ideas but because their idea is to sell the company.

In earlier times before Social Media, it was generally only firms which wished to be bought (or were publicly traded) which engaged in this behavior. However, now that everything is branding, practically every company plays the game unless they are largely immune to market forces.

Even then, large hegemonic institutions still do this, as it tends to be the cheapest way to purchase influence and legitimacy. See the Postscript on the Societies of Control by Deleuze for why this is. You see ridiculosity such as videos of soldiers dancing the macarena with the hapless peasants whose countries they occupy because of this.

Reinforcing that trend is that the corporate environment is almost always the same as the large hegemonic institutions, just in miniature. They tend to "ape their betters" as yet another way of demonstrating higher value.

However, "Clout Chasing" (also known as the "maximizing reach" marketing model -- see Rory Sutherland on this here) has largely reached the point of diminishing returns. This model is generally feast-or-famine; Understanding this makes you realize why the FAANG stocks are nearly a fifth of the S&P market cap. Yet many continue to willingly barrel into a game with an obvious pareto distribution without the slightest clue "what it takes" to actually enter that rarified stratosphere.

So, they make the same mistake of so many others by thinking "oh, I'll just look like the top 20% and it'll work, right?" Few understand that luxury brands are built by being different from everything else, which also means your company culture has to be different too. So, they hire an army of "Scrum Consultants", "Facilitators" and Managers to fit their dollop of the workforce into Procrustes' Bed. Millions of wasted hours in meetings and "Team Building" exercises later, the ownership starts wondering why it is their margins keep going down and that they have trouble retaining top talent.

It's the same story in the Hegemonic Institutions; Muth's Command Culture describes in great detail how the US Army (and eventually the Prussians too!) made the mistake of thinking the structure of the Prussian military was why it won, when in reality it was their highly skilled people and a lack of organizational barriers to their pursuit of success. The Map is not the territory, as they say.

All institutions are only as successful and righteous as their members can allow them to be. Most skilled people rarely consider exit until institutional barriers prove to hinder their ability to excel (which is freedom -- the ability to self-actualize). The trouble is that optimizing for Clout and Reach means that all controversial thought must be purged, as Guilt by Association is considered a lethal risk to your reach.

In reality it actually isn't -- "Love me, Hate me, but Don't Forget me" is actually the maxim which should be adopted; Oracle would have long since shuttered if having a negative reputation had anything to do with your reach. Being a source of Indignation is as effective as being a source of Comfort to build reach.

However this is not a common strategy due to the fact that most employees don't want to feel like they work for a devil. So, we instead get this elaborate maze of mission statements, team building and "CULTure" to brainwash the drones into believing whatever the company does is the "right thing" TM. Any employees who look at things objectively are demonized as being "Toxic" and "Negative" and ultimately pushed out.

The trouble with this is that the most effective people only get that way by seeing the world for what it is, and then navigating appropriately. When put into an environment where they know they cannot freely express themselves this is a high default level of friction that means they'll be ready to bolt or blow up over minor things. It takes but a straw to overburden a person walking upon eggshells.

From the perspective of the effective worker, most corporate simply looks like a crab bucket. Any person who tries to excel (make it to the top of the bucket and possibly out) is immediately grabbed by the other crabs and forced back down into their little hell. This is horrific for productivity, as it means the organization is not pulling forward, but in all directions (which means they are effectively adrift).

Which brings us back to the whole "lack of better ideas than getting sold" thing. This condition is actually tolerable when attempting to sell a firm; prospective buyers rarely care about the opinions or conditions of the line workers. But with the advent of this being 24/7 365 for nearly all firms and institutions this stultification has become inescapable save for under the ministrations of "Turnaround Capital". These firms buy cheap and hated firms, stop playing house and start making money by unleashing their people to be productive. They don't have to resort to the usual nonsense because turning a failure into a success is enough of a demonstration of higher value.

Given enough time though, all companies have their growth level off and become boring. This is when these veblenesque measures take hold, as familiarity is lethal to attraction (building that is the essence of sales). It's like secondary sex characteristics for figments of our imagination which are legally people.

It's that last bit that's where the rub lies and why it all goes so wrong. The Principal-Agent problem is fundamentally why the people running firms end up screwing up their charge worse than child beauty pageant contestants. The whole reputational protection of the employees (need to see themselves as good) referred to earlier is one such case.

Here are a few others (list is by no means complete):

Stock Buybacks:
If the argument used that "there is no better use of our money than to speculate in our own stock" (WOW what a flex) were actually true, they'd issue a dividend and tell their shareholders as much. The fact that this is not what is done tells you most of what you want to know. The reality has to do with an executive team's compensation tied to share price.

Shiny new Office:
While workers tend to expect a minimum level of comfort, the level required for high performance to take hold is far lower than believed. Repeated study has shown there to be little difference in productivity for nonwork facilities such as cafeterias, break rooms, gyms and game rooms. The actual reason is that the agents want more a social club, and to attract different types of (read: nonproductive) workers, as essentially paid friends.

Core Values, COCs, etc
It is worth noting that the pursuit of profit or regard for shareholders and customers is almost never in any of these statements. Rather than assume you are working with adults who can figure their intrapersonal problems out, let us build a procrustes' bed so that we'll never have any problems! This kind of contract to adhere to vague values (which frequently contradict religious convictions and are immaterial to the work) can never be seen as anything but an unconscionable contract of adhesion.

It is very similar to the ridiculous "Covert Contract" concept (If I do X in Y emotional relationship it will be trouble free), and nobody is shocked at it's repeated failure. Nevertheless endless amounts of time is wasted on nonsense like this which ultimately serves only as more roadblocks to productivity (and thus impetus for the productive to exit). Similarly, many lower status employees use it as a club against higher status ones; this is the essence of the crab bucket.

For example, a common theme in CoCs & "Values" statements is that colleagues must be shown "respect" of which the level is never clearly defined. It then becomes a mystery why bad ideas proliferate as "well golly gee we gotta respect everyone" regardless of whether such respect is merited or not.

This sort of application of the "reach" marketing philosophy (I have a hammer, everything is a nail) is especially ridiculous here. Let us be so open minded our brains fall right out. Let us be so approachable that we get mugged in broad daylight. Let us kowtow to our agents (especially the lawyers) such that our shareholders are ruined.

Were people rational about these things, they would simply insure employees against lawsuits resulting from interpersonal conflicts and move on. It'd be a lot cheaper than letting lawyers strangle your corporation with rules. It would also still weed out the truly troublesome via excessive premium increases. However, that acknowledges human nature and imperfection, which is not how the agents want to see themselves -- they are all angels, and like to virtue-signal. Management sees all this money they are "saving" avoiding lawsuits but forget the money left on the table by the talent which left or was forced out.

Ultimately all these statements of virtue are actually signals of luxury and lesiure (abundance). People in conditions of scarcity cannot afford to let principle get in the way of profit. Which makes many of these things emanating from firms with high Debt to Income ratios, triple digit P/Es and yield below 1% seem quite bizarre.

There are a few other dubiously useful things like offsite meetings and dinners, but that can be reasonably justified as compensation (but that avoids taxation, yay). The most complete picture of the situation was written when I was but four years old: The Suicidal Corporation. Give it a gander when you get a chance.

Knowing all this, what then shall we do? By and large, the productive have chosen exit or been forced into entrepeneurship. The whole "rise of the hustle economy" is pretty much exactly this. Instead of the corporate strategy of maximizing reach, they instead maximize engagement. Which is a hell of a lot more effective in the long run.

Which means that as a high-talent person you too have to play the "Demonstrating Higher Value" game, rather than be able to toil and build wealth in obscurity. So rather than lament the situation I have decided instead to embrace it. In retrospect, it is really the only path left for growth as I have more or less reached the highest echelons of compensation as a software developer for my market already.


Getting & Staying productive πŸ”—
1619795891  

🏷️ video
One of the things that comes up repeatedly in your career is whether your edge is sharpening or dulling.  It is very easy to get into a pattern when you are at work and not have to change your approach, especially if you are performing at or near the top of the pack.  You can always find a way to improve performance though, and a lot of times it's "I should (and do) know better, but haven't applied the knowledge yet".

Ultimately the problem is called Metaheuristics. Viewed in aggregate, the learning behaviors of programmers is a sort of Memetic Algorithm.  This must also be balanced against the constraints of the current economy.  Namely that for many problems, throwing hardware at the problem is cheaper than throwing money at programmers to fix the problem with existing hardware. This means that you have to converge on "good enough" and "worse is better", and tools/methods that don't fight this.  This can be hard to do emotionally for engineers, as we all want to build great works.

What seems to work best for me these days is writing essays about systems I'm considering designing, and revising it as I go to make sure the narrative remains consistent.  I feel this actually captures the idea of "user story" a lot better than the trite one-liners which are actually user acceptance criteria.  Storytelling is how our brains actually work, and initially the story is hopes and predictions (much like the program going with it) eventually annealed down to reflect reality.  It doesn't hurt that this builds documentation and test cases as part of the process.

Crush everything getting in the way of work

The first thing I had to master as a programmer was how to build an environment which prevents or mitigates the costs of interruptions to flow state. Much of this involved learning interfaces and editors such that I could largely do my job without expensive context switches, like having to touch a mouse.  The two best means I've found to do this are Tmux + Vim + guake-ish terminal emulators and VSCode + virtual desktops with most the IDE features like suggestions turned off.

There are 4 primary features here:
  • 100% keyboard controlled navigation and context switches
  • Good syntax hinting
  • Remembering state between sessions
  • Full-screened everything, focus on only one thing at a time

I have to have a solution for every major OS, as this is a necessity for testing software properly.

On Linux I can pretty much have everything I need on one to N+1 virtual desktops, where N is the number of browsers I need to support, plus one window for doing documentation lookups (usually on a second monitor).  I do most of my development on linux for precisely these reasons.

This mostly works the same on OSX, however they break immersion badly for people like me by having:

  • different and non-configurable keyboard shortcuts
  • window decorations in nonstandard locations
  • virtual desktops no longer have consistent ordering between sessions after 10.6.

They seemed to have optimized for the user which Alt+Tabs between 700 open windows in one workspace, which is a good way to break flow with too much clutter.

On windows VSCode has a good shortcut to summon the console, and a vim plugin that works great.  Windows virtual desktops also work correctly, and I can configure shortcuts on linux to match both it and the console summon.  I would prefer it be the other way around, but commercial OS interface programmers tend to either be on a power trip or too afraid of edge cases to allow user choice.  If I could have a tmux-like shortcut to page between vscode workspaces like I do panes I'd be a happier camper, as I wouldn't need as many virtual desktops.  This is apparently their most popular feature request right now, go figure.

_ is other people

Beyond the little hacks you do to increase flow, mitigating the impact of interruptions is important.  This is a bit more difficult as much of this is social hacking, and understanding human nature.  The spread of computing into the general population has also complicated this as the communication style of the old order of programmers and engineers is radically different from the communication norms of greater society.  I, along with much of the rest of my peers, are still having a difficult time adjusting to this.

The preferred means of communication for engineers is information-primary rather than emotion-primary.  The goal of communication is to convey information more than feeling (if that is communicated at all).  The majority of society does not communicate in this fashion.  Instead they communicate Feelings above all else and Information is of secondary (if any) importance.  For these people, their resorting to communication of information is a sign of extreme frustration that you have not yet discerned their emotional message and provided the validation they are seeking.  If they haven't already lashed out, they are likely on the way to doing so.

It is unfortunate to deal with people operating via a juvenile and emotional communication style, but this is the norm in society and over the last 20 years has entirely displaced the existing engineering culture at every firm I've worked at.  Code reviews can no longer be blunt, as it is unlikely you are dealing with someone who is not seeking validation as part of their communication packets.  The punky elements of culture which used to build friendship now build enmity (as you are dealing with the very "squares" nerd culture embraced being hated by).  Nevertheless, you still have to walk in both worlds as there are still islands of other hackers just like you.  Taking on and off these masks is risky, however, as people are quite hung up on the notion that people must be 100% congruent with how they are perceived outwardly.

Everyone out there building an online reputation learns quickly that courting controversy will get you far more engagement than if you had made a program which cured eczema and allowed sustained nuclear fusion. Emotional highs punctuated by warm fuzzies is generally the sort of rollercoaster ride people are looking for.  Lacking better sources they will attempt to draw you into their emotional world.  Their world is a skinner box, and if you enter it unawares they will train you.

They cannot help but be this way, so you must turn off the part of your brain that looks for informational meaning in mouth noises.  Their utterances have little more significance than the cries of a baby or yapping of a dog.  The only thing that matters is whether the behavior is something you wish to encourage or discourage.

Operant conditioning is how you must regard your interactions.  When you validate them (usually with attention), you encourage that behavior.  Non-acknowledgement of irritating behavior is often the most effective discouragement.  The natural urge to engage with any point of fact must be resisted, and instead reserve your communications to that which advances your purposes.  When a minimum of validation is demanded, dismissal via fogging, playing dumb or broken-record technique should be engaged in, rather than the hard no which is deserved.  A satisfying response to a demand can never be offered unless you wish to be dominated by those around you.  Many see this as Machiavellian manipulation, but there is no choice.  You play this game, or get played.

Nevertheless, this all has a huge effect on the corporate environment.  Most firms devote far more energy to playing house than pleasing customers and making money, and tech is no exception.  Fellow employees are far more likely to seek validation on this schedule with each other than customers.  Most satisfy themselves with a stable IV drip of validation from their local group rather than experiencing the much more rewarding experience of solving customer problems.  This should come as no shock, given social media is purpose built to inculcate this mental model, as it was found to maximize engagement.  This is also good at building solidarity at a firm but unfortunately comes at the expense of crowding-out emotional investment in the customer who cannot and should not have so tight an OODA loop with their vendors.


Selling Internally, Externally and in Interviews πŸ”—
1619710468  

🏷️ video
One of the common interview questions you get is the time preference question.  I've asked it myself multiple times. It goes something like this:

  • Tell me about a time you had to make sacrifices in the short term to achieve a long term goal.
Engineering companies very much want to think of themselves as builders of great works made to stand the test of time. They frequently fall short of this as the customer generally wants "Mr. Right Now" instead of "Mr. Right". Wise organizations achieve coherence in their strategic vision by having "fulfill customer desires" itself as the long term goal. I've mentioned before that a vision which does not align with the core business model is doomed to failure, and many companies fall into this trap.

Many view the agglomeration of technical debt associated with an iterative design process to be short-term thinking which undermines the long term...but that assumes the goal is to build quality software. In reality, the goal is to build software of acceptable quality that satisfies customer needs; worse is better.  In this framework, much of what goes on at an engineering corporation can be framed as a victory rather than a death march.  The problem to solve then becomes minimizing the iteration duration of your OODA loop.

The OODA loop of a software enterprise is basically this:
  1. Observe: Sample reaction to the latest software version
  2. Orient: Refine program and development schedule constraints based on reaction
  3. Decide: Choose optimal algorithms to satisfy new and changed constraints
  4. Act: Test, Anneal and Release

You break out of your loop when you stop getting meaningful observations.  Many organizations have successfully adopted this (see OPCDA).  The whole point here is that you accumulate less bad designs lurking in your code, as you can refine constraints quickly enough to not over-invest in any particular solution.

Many times this is paired with other questions to find out how much of a self-starter, leader or entrepreneurial aspect you have:

  • How do you drive adoption for your ideas?
  • How do you measure adoption of your ideas?

They always want a concrete example from your past employment, and it needs to be your thing from start to finish. This is usually also a good opportunity to reinforce how well you embrace iterative design principles.  In fact it drives at the real reason they ask the first question.

Knowing how to drive adoption and measure it is key to the observation phase.  If your observations are flawed, it will poison and invalidate the results of all resultant phases, so you need to get it right.

There are two primary adoption strategies. All marketing is a tree search algorithm of one sort or another thanks to the way influence networks work.

Breadth first vs Depth First Marketing

You can either drive adoption of something within an organization virally (infect the sheep) or evangelically (convert the Shepard).  You can do both, but conditions usually mean you need to lean primarily towards one or the other.
The cost of reaching consumers is directly is a great deal higher, and they have a lot less to spend than businesses and bosses with budgets.  That said, the total revenue you can get from targeting retail is vastly larger, and defections from the product are less troublesome.

In general you see a hybrid model nowadays where an open source (or reduced price) component is marketed towards retail, and a paid premium version is marketed towards business.
Infection of the sheep can drive conversion of the Shepard, much the way that conversion of the Shepard can drive the flock.

When it comes to driving change within organizations, the formula is turned upon its' head.  It is actually cheaper to convert fellow drones instead of the queen, and effect a coup de main. The drones are used to collaborating with each other and value each others' input far more than they do tools provided from above. Similarly, management is incapable of understanding many of the problems which occur in the production process as they happen, supposing they even look for them at all.  Furthermore, getting the kind of feedback needed to iterate and improve is fast and straightforward between drones.

This is why much of the approach around things like Kaizen and Scrum focus on empowering the drone to streamline production themselves.  The concept is generally referred to as Metis, and it is valuable for management to periodically inspect and experiment with cross-pollination of this across divisions to increase productivity.

War story time

For those of you not familiar with me, I have a decade of experience automating QA processes and testing in general.
This means that the vast majority of my selling has been of two kinds:

  • Selling tactical/strategic/logistic intelligence reports
  • Selling colleagues on tools to improve their productivity

That said, I also wore "all the hats" in my startup days at hailstrike, and had to talk a customer down from bringing their shotgun to our office.
I handled that one reasonably well, as the week beforehand I'd read Carl Sewell's Customers for Life and Harry Browne's Secret of selling anything.
The problem was that one of the cronies of our conman CEO was a sales cretin there and promised the customer a feature that didn't exist and didn't give us a heads up.
It took me a bit to calm him down and assure him he was talking to a person that could actually help him, but after that I found out what motivated him and devised a much simpler way to get him what he wanted.
A quick code change, a deploy and call back later to walk him through a few things to do on his end to wrangle data in Excel and we had a happy camper.

He had wanted a way to bulk import a number of addresses into our systems and get a list of hailstorms which likely impacted the address in question, and a link into our app which would pull the storm map view immediately (that they could then do a 1-click report generate for homeowners).

We had a straightforward way of doing this for one address at a time, but I had recently completed optimizations that made it feasible to do many as part of our project to generate reports up to two years back for any address.
Our application was API driven and already had a means to process batched requests, so it was a simple matter of building an excel macro talking to our servers which he could plug his auth credentials into.
I built this that afternoon and sent it his way.  This started a good email chain where we made it an official feature of the application.

It took a bit longer to build this natively into our application, but before the week was up I'd plumbed the same API calls up to our UI and this feature was widely available to our customers.
I was also able to give a stern talking to our sales staff (and gave them copies of C4L and SSS) which kept this from happening going forward, but the company ultimately failed thanks to aforementioned conman CEO looting the place.

The war within

After that experience I went back to being a salaryman over at cPanel.  There I focused mostly on selling productivity tools internally until I transitioned into a development role.

I'd previously worked on a system we called "QAPortal" which was essentially a testing focused virtual machine orchestration service based on KVM.  Most of the orchestration services we take for granted today were in their infancy at that time and just not stable or reliable enough to do the job.  Commercial options like CloudFormation or VSphere were also quite young and expensive, so we got things done using perl, libvirt and a webapp for a reasonable cost.  It also had some rudimentary test management features bolted on.

That said, it had serious shortcomings, and the system essentially was unchanged for the 2 year hiatus I had over at hailstrike as all the developers moved on to something else after the sponsoring manager got axed due to his propensity to have shouting matches with his peers.
I was quickly tasked with coming up with a replacement.  The department evaluated test management systems and eventually settled on TestRail, which I promptly wrote the perl API client for and put it on CPAN.
The hardware and virtual machine orchestration was replaced with an openstack cluster, which I wrote an (internal) API library for.
I then extended the test runner `prove` to talk to and multiplex it's argument list over the various machines we needed to orchestrate and report results to our test management system.
All said, I replaced the old system within about 6 months.  If it were done today, it would have taken even less time thanks to the advances in container orchestration which have happened in the intervening time.  The wide embrace of SOAs has made life a lot better.

Now the team had the means to execute tests massively in parallel across our needed configurations, but not every team member was technical enough to manage this all straightforwardly from the command line.  They had become used to the old interface, so in a couple of weekends I built some PHP scripts to wrap our apps as an API service and threw up a jQuery frontend to monitor test execution, manage VMs and handle a few other things the old system also accomplished.
Feedback was a lot easier than with external customers, as my fellow QAs were not shy about logging bugs and feature requests.

I suspect this is a lot of the reason why companies carefully cultivate alpha and beta testers from their early adopter group of rabid fans.  Getting people in the "testing mode" is a careful art which I had to learn administering exploratory test sessions back at TI, and not to be discarded carelessly.  That is essentially the core of the issue when it comes to getting valid reports back from customers.  You have to do Carl Sewell's trick of asking "what could have worked better, what was annoying...", as those are the sort of user feedback that you want rather than flat-out bugs.  Anything which breaks the customers' immersion in the product must be stamped out -- you always have to remember you are here to help the user, not irritate them.

Rewarding these users with status, swag and early access was the most reliable way to weed out time-wasters; you only want people willing to emotionally invest, and that means rewards have to encourage deeper integration with the product and the business.  It also doesn't hurt that it's a lot cheaper and easier to justify as expenses than bribes.

Are ya winning son?

Measuring adoption of software and productivity ideas in general can be tricky unless you have a way to either knock on the door or phone home. Regardless of the approach taken, you also have to track it going forwards, but thankfully software makes that part easy nowadays.
Sometimes you use A/B tests and other standard conversion metrics, as I used extensively back at HailStrike.  I may have tested as much copy as I did software!  Truly the job is just writing and selling when you get down to it.

In the case of inter-organization projects most of the time it's literally knocking on the door and talking to someone.  At some level people are going to "buy" what you are doing, even if it's just giving advice.  This is nature's way of telling you "do more of this, and less of the rest".

I can say with confidence that the best tool for the job when it comes to storing this data is a search engine, as you eventually want to look for patterns in "what worked and didn't".  Search engines and Key-Value stores give you more flexibility in what IR algorithm best matches the needs of the moment.  I use this trick with test data as well; all test management systems use databases which tend to make building reports cumbersome.

Time Preference versus Subjective Value

Rather than flippantly dismiss the original question, I would like to revisit the problem.  While it is obvious that I will probably gain more over the long term by sacrificing my desire to do something fun instead of writing this article, one must also take into consideration the law of diminishing marginal utility and the Paradox of Value.  Thinking long term means nothing when one is insolvent or dead without heirs tomorrow.  There will always be an infinite number of possible ends for which I sacrifice my finite means.  As an optimization problem, it is NP hard.  The best we can do is to use the Kelly Criterion to distribute our time and other assets wisely among the opportunities we best understand the risks about.

Building an online reputation is quite expensive and time consuming, but is beginning to pay off.  It doesn't hurt that I'm pursuing multiple aims simultaneously (building a MicroISV product, chasing contracts) with everything I write these days.  That said it cannot be denied that hanging out your shingle is tantamount to a financial suicide mission without multiple years of runway.  Had I not spent my entire adult life toiling, living below my means and not taking debts, none of this would be possible.  In many ways it's a lot like going back to college, but the hard knocks I'm getting these days have made me learn a whole lot more than a barrel full of professors.

For those who insist on the technical answer to this question, I would direct you to observe the design of Selenium::Client versus that of Selenium::Remote::Driver.  This is pretty much my signature case for why picking a good design from the beginning and putting in the initial effort to think is worth it.  My go-to approach with most big balls of mud is to stop the bleeding with modular design.  Building standalone plugins that can ship by themselves was a very effective approach at cPanel, and works very well when dealing with Bad and Right systems.  What is a lot harder to deal with is "Good and Wrong" systems, usually the result of creationist production.  When dealing with a program that puts users and developers into Procrustes' bed rather than conforming to their needs you usually have to start back from 0.  Ironically most such projects are the result of the misguided decision to "rewrite it, but correctly this time".

Given cPanel at the time was a huge monorepo sort of personifying "bad design, good execution", many "lets rewrite it, but right this time" projects happened and failed, mostly due to having forgotten the reasons it was written the way it had been in the first place.  New versions of user interfaces failed to delight users thanks to removing features people didn't know were used extensively or making things more difficult for users in the name of "cleaner" and "industry standard" design.  A lot of pain can be brought to a firm when applying development standards begins to override pleasing the customer.  The necessity of doing just that eventually resulted in breaking the monolith to some extent, as building parallel distribution mechanisms was the only means to escape "standardization" efforts which hindered satisfying customer needs in a timely manner.

This is because attempting to standardize across a monorepo inevitably means you can't find the "always right" one-size fits-all solution and instead are fitting people into the iron bed.  The solution of course is better organizational design rather than program design, namely to shatter the monolith.  This is also valuable at a certain firm scale (dunbar's number again), as nobody can fit it all into their head without resorting to public interfaces, SOA and so forth.  Reorientation to this approach is the textbook example of short-term pain that brings long-term benefit, and I've leveraged it multiple times to great effect in my career.


Telling Stories in corporate πŸ”—
1619450039  

🏷️ video
Ever since I struck out on my own, I have done far more job interviews and storytelling than my entire carrer thus far. Most of the interviews for development and testing contracts are talking through problems. You must tell a story, as this is how people internalize your attributes and form emotional investments. I can talk about the lessons I have learned or the design of a thing until I am blue in the face, but it won't matter unless people know where they come from. This is because people don't engage the predictive engine in their hindbrain if you don't tell a story.  People don't get into camshaft thinking (imagination) without emotionally investing in knowing how it works, and as such are in "internal monologue" mode.  A story is the only thing this mode of thought can comprehend or output, and is a necessary prerequisite to get into the mode of thinking where imagination "fills in the blanks".  By the time they get there, diagrams and so forth are unnecessary unless you are interested in mass production which comes for free with software.

When you tell a story, people "think past the sale", and start to see themselves doing business with you unconsciously. If you don't tell a story, people default to their lower consciousness which is stimulus-response. In this case, if you aren't attractive from a "mode 1" (judging a book by it's cover) point of view, good luck. For knowledge work, this is only the case for established intellectuals with some degree of fame. This is why everyone has to do this "online brand" thing; eventually somebody fishing will see you in the net and haul you in.

That said, your online brand can only get you in the door. From there people in the knowledge trades have an innate skepticism beaten into them via the scientific method. This has to be overcome, and the way this is usually done is by telling stories which the interlocutor identifies with. The whole goal is for both the interviewer and evaluator to be congruent with what each expects from the other. This is why it always ends up being the senior development staff that does the heavy lifting here. They've heard this story enough times to sniff out the little details that break them out of their suspension of disbelief (also known as "benefit of the doubt"). This has a high rate of success, as it is difficult to fake having reasoning skills, and being able to practically apply them. It's also difficult to fake the little details which we encounter in the course of our daily toil. Difficult, but not impossible.

I remember setting up those little programming puzzles on hackerrank for the candidates to chew through. My colleague who was working on this with me on it at the time had some anxiety as to whether they were being specific enough in the description of the problems. I thought of how the application process ought to feel both to the applicant and evaluator in order to maximize the potential they can show and give ample opportunity to display their deficiencies. The job-seeker's story is supposed to be a gauntlet of increasing difficulty, hopefully revealing the core qualities needed in our work.

In that vein, I suggested we nail down problem 1 as well as possible, while leaving the second vague. This gives people the ability to show both how efficiently they operate when things are concrete and how quickly they pick up on our "trick" question which is ill-defined and start giving us options. The two hardest problems in software are choosing optimal algorithms and reducing vague requirements into concrete, testable execution constraints. Everything else is straightforward testing, investigation and annealing.

This is not to say that software organizations don't have other (mostly logistical and marketing) problems to solve, but that these are the core ones of interest to engineering. As an interviewer you have to lead the horse to water and see if they'll drink. The interviewer should focus on getting them interested enough in their stories that the evaluator shares some back. Reciprocity is the best sign of developing emotional investment.

You may have noticed I'm telling a story right now. It's uncanny how well this works on you even when you know how the sausage is made! I've been on both sides of the table when it's clear that "they know you know, and you know they know" based on the responses. In these cases breaking the fourth wall is even more convincing of a story as it too is a story.

This is unfortunately a rarity on both sides. All the world's a stage, and we are merely players. A performance cannot truly be great unless both sides can believe it and find more significance therein than their reality! This is despite foreknowledge that it's a performance and not a demonstration. To succeed, one has to get fully sheep-dipped into the hyperreality you want to hop into.

On that note, I will be putting out a series of war stories soon both as practice for upcoming contracts and for your enjoyment.

April 2021 Houstonpm: pairwise technicals πŸ”—
1618337136  

🏷️ video 🏷️ pairwise
A re-record of the technical and maths-heavy aspects of my April 2021 Houston.pm presentation.

Hard Problems πŸ”—
1618336942  

🏷️ pairwise

When preparing any tool which you see all the pieces readily available, but that nobody has executed upon, you begin to ask yourself why that is. This is essentially what I've been going through building the pairwise tool.

Every time  I look around and don't see a solution for an old problem on CPAN, my spider-senses start to fire.  I saw no N-dimensional combination methods (only n Choose k) or bin covering algorithms, and when you see a lack of N-dimensional solutions that usually means there is a lack of closed form general solutions to that problem.  While this is not true for my problem space, it rubs right up against the edge of NP hard problems.  So it's not exactly shocking I didn't see anything fit to purpose.

The idea behind pairwise test execution is actually quite simple, but the constraints of the software systems surrounding it risk making it more complex than is manageable. This is because unless we confine ourselves to a very specific set of constraints, we run into not one, but two NP hard problems. We could then be forced into the unfortunate situation where we have to use Polynomial time approximations.

I've run into this a few times in my career. Each time the team grows disheartened as what the customer wants seems on the surface to be impossible. I always remember that there is always a way to win by cheating (more tight constraints). Even the tyranny of the rocket equation was overcome through these means (let's put a little rocket on a big one!)

Breaking it down

The first problem is that N-Wise test choosing is simply a combination.
This results in far, far more platforms to test than is practical once you get beyond 3 independent variables relevant to your system under test. For example:

A combination with 3 sets containing 3, 5 and 8 will result in 3 * 5 * 8 = 120 systems under test! Adding in a fourth or fifth will quickly bring you into the territory of thousands of systems to test.  While this is straightforward to accomplish these days, it is quite expensive.

What we actually want is an expression of the pigeonhole principle.  We wish to build sets where every element of each component set is seen at least once, as this will cover everything with the minimum number of needed systems under test.  This preserves the practical purpose of pairwise testing quite nicely.

In summary, we have a clique problem and a bin covering problem. This means that we have to build a number of bins from X number of sets each containing some amount of members. We then have to fill said bins with a bunch of tests in a way which will result in them being executed as fast as is possible.

Each bin we build will represent some system under test, and each set from which we build these bins a particular important attribute. For example, consider these sets:

  • Operating Systems: Windows, Linux, OSX
  • Processor Architecture: 32-bit, 64-bit
  • Browser: Firefox, Chrome, Safari, Brave, Opera, SeaMonkey

A random selection will result in an optimal multi-dimensional "pairwise" set of systems under test:

  1. Firefox - Windows - 64 Bit
  2. Chrome - Linux - 32 Bit
  3. Safari - Windows - 32 Bit
  4. Brave - OSX - 32 Bit
  5. Opera - OSX - 64 Bit
  6. SeaMonkey - Linux - 64-Bit

The idea is to pick one of each of the set with the most members and then pick from the remaining ones at the index of the current pick from the big set modulo the smaller set's size. This is the "weak" form of the Pigeonhole Principle in action, which is why it is solved easily with the Chinese remainder theorem.

Sometimes you can oversimplify

You may have noticed that perhaps we are going too far with our constraints here. This brings in danger, as the "strong" general form of the pigeonhole principle means we are treading into the waters of Ramsey's (clique) problem. For example, if we drop either of these two assumptions we can derive from our sets:

  1. No element of any given set is repeated
  2. No element of any given set is shared with another

We immediately descend into the realm of the NP hard problem. This is because we are no longer a principal ideal domain and can no longer cheat using the Chinese remainder theorem. In this reality, we are solving the Anti-Clique problem specifically, which is particularly nasty. Thankfully, we can consider those two constraints to be quite realistic.

We will have to account for the fact that the variables are actually not independent. You may have noticed that some of these "optimal" configurations are not actually realistic. Many Operating systems do not support various processor architectures and software packages. Three of the configurations above are currently invalid for at least one reason.  Consider a configuration object like so:

my $conf = {
    PlatformGroups => {
        'Operating Systems' => [qw{CentOS Ubuntu Windows OSX}],
        'CPU Archetechure'  => [qw{32-bit 64-bit}],
        'Browser'           => [qw{Firefox Opera Safari Chrome Iexplore Brave Dillo lynx}],
        'Mail Server'       => [qw{exim courier postfix qmail exchange}],
        'HTTP Server'       => [qw{ngnix apache lighttpd thttpd}],
        'Database Server'   => [qw{postgres mysql mariadb mssql oracle}],
        'Message Queue'     => [qw{rabbitmq zmq}],
        'Search Engine'     => [qw{solr lunr elasticsearch}],
    },
    incompatibilities => {
        'Windows' => [qw{32-bit Safari Dillo qmail exim courier postfix thttpd solr}],
        'OSX'     => [qw{32-bit Iexplore}],
        'CentOS'  => [qw{Iexplore}],
        'Ubuntu'  => [qw{Iexplore}],
    },
};
Thanks to the requirement that all configurations be unique, we can use a simplified data structure here rather than over-complicating the PlatformGroup data structure (and our processor code).

Can we throw away these configurations without simply "re-rolling" the dice?  Unfortunately, no.  Not without using the god algorithm of computing every possible combination ahead of time, and therefore already knowing the answer.  As such our final implementation looks like so:

sub cliques($conf,$tests) {
    my %pgroups = ref $conf->{PlatformGroups} eq 'HASH' ? %{$conf->{PlatformGroups}} : ();
    my @plans;

    # Randomize the ordering of the platform groups for eventual consistency.
    foreach my $pg (keys(%pgroups)) {
        @{$pgroups{$pg}} = shuffle(@{$pgroups{$pg}});
    }

    # The idea here is to have at least one pigeon in each hole.
    # This is accomplished by finding the longest list of groups, and then iterating over everything we have modulo their size.
    my $longest = (sort { scalar(@{$pgroups{$b}}) <=> scalar(@{$pgroups{$a}}) } keys(%pgroups))[0];
    my $llen = scalar(@{$pgroups{$longest}});
    my $tot = scalar(@$tests);

    # Bin covering
    my $remainder = ( $tot % $llen );
    my $to_take = int$tot / $llen);
    my $offset = 0;

    for (my $i=0; $i < $llen$i++) {
        my @newplats;
        foreach my $pgroup ( sort { scalar(@{$pgroups{$b}}) <=> scalar(@{$pgroups{$a}}) } keys(%pgroups)) {
            my $idx = $i % scalar(@{$pgroups{$pgroup}});
            my $orig_idx = $idx;

            # If a partial is invalid, we must re-roll the dice.
            while (!combination_valid($conf@newplats, ,$pgroups{$pgroup}[$idx])) {
                $idx = ($idx + 1) % scalar(@{$pgroups{$pgroup}});
                # Allow for 'incomplete' sets omitting a configuration group entirely due to total incompatibility
                last if $idx == $orig_idx;
            }
            push(@newplats,$pgroups{$pgroup}[$idx]);
        }
push(@plans, \@newplats);
    }
    return \@plans;
}

sub combination_valid ($conf,@combo) {
    my %compat = %{$conf->{incompatibilities}};
    foreach my $key (keys(%compat)) {
        next unless ref $compat{$keyeq 'ARRAY';
        my @compat = grep { my $element = $_defined $element && ( $element eq $key || grep { $element eq $_ } @{$compat{$key}} ) } @combo;
        return 0 if @compat > 1;
    }
    return 1;
}

This brings us to another unmentioned constraint: what happens if a member of a set is incompatible with all members of another set?  It turns out accepting this is actually a significant optimization, as we will end up never having to re-roll an entire sequence.  See the while loop above.

Another complication is the fact that we will have to randomize the set order to achieve the goal of eventual coverage of every possible combination. Given the intention of the tool is to run decentralized and without a central oracle other than git, we'll have to also have use a seed based upon it's current state.  The algorithm above does not implement this, but it should be straightforward to add.

Filling the bins

We at least have a solution to the problem of building the bins. So, we can move on to filling them. Here we will encounter trade-offs which are quite severe. If we wish to accurately reflect reality with our assumptions, we immediately stray into "no closed form solution" territory. This is the Fair Item Allocation problem, but with a significant twist.  To take advantage of our available resources better, we should always execute at least one test. This will result in fewer iterations to run through every possible combination of systems to test, but also means we've cheated by adding a "double spend" on the low-end.  Hooray cheating!

The fastest approximation is essentially to dole out a number of tests equal to the floor of dividing the tests equally among the bins plus floor(  (tests % bins)  / tests ) in the case you have less tests than bins. This has an error which is not significant until you reach millions of tests. We then get eaten alive by rounding error due to flooring.

We could simply add the remainder and give up on fair allocation.  But given the remainder will always be lower than the number of bins, we can just shave one off of it each go-through until we run out (while still retaining the minimum bound of 1).  This is is the optimal solution:

my $choose = int( $total_tests / $bins );
my $remainder = $total_tests % bins;
...
# later in our loop
my
 $take = $choose + ( $remainder && 1 ) || 1;
$remainder-- if $remainder;

From there we simply splice out the relevant elements from the array of tests.  The completed algorithm has some minor differences from cliques() above:

sub cliques($conf,$tests) {
    my %pgroups = ref $conf->{PlatformGroups} eq 'HASH' ? %{$conf->{PlatformGroups}} : ();
    my @plans;

    # Randomize the ordering of the platform groups for eventual consistency.
    foreach my $pg (keys(%pgroups)) {
        @{$pgroups{$pg}} = shuffle(@{$pgroups{$pg}});
    }

    # The idea here is to have at least one pigeon in each hole.
    # This is accomplished by finding the longest list of groups, and then iterating over everything we have modulo their size.
    my $longest = (sort { scalar(@{$pgroups{$b}}) <=> scalar(@{$pgroups{$a}}) } keys(%pgroups))[0];
    my $llen = scalar(@{$pgroups{$longest}});
    my $tot = scalar(@$tests);

    # Bin covering
    my $remainder = ( $tot % $llen );
    my $to_take = int$tot / $llen);
    my $offset = 0;

    for (my $i=0; $i < $llen$i++) {
        my @newplats;
        foreach my $pgroup ( sort { scalar(@{$pgroups{$b}}) <=> scalar(@{$pgroups{$a}}) } keys(%pgroups)) {
            my $idx = $i % scalar(@{$pgroups{$pgroup}});
            my $orig_idx = $idx;

            # If a partial is invalid, we must re-roll the dice.
            while (!combination_valid($conf@newplats, ,$pgroups{$pgroup}[$idx])) {
                $idx = ($idx + 1) % scalar(@{$pgroups{$pgroup}});
                # Allow for 'incomplete' sets omitting a configuration group entirely due to total incompatibility
                last if $idx == $orig_idx;
            }
            push(@newplats,$pgroups{$pgroup}[$idx]);
        }

        my $tt = $to_take + ( $remainder && 1 ) || 1;
        push(@plans,{ tests => [splice(@$tests, $offset$tt)], platforms => \@newplats });
        $remainder-- if $remainder;
        $offset += $tt;

        # Just repeat tests in the event we have more SUTs available than tests
        $offset = $offset % $tot;
    }
    return \@plans;
}

It is worth noting there is yet another minor optimization in our production process here at the end, namely that if we have more systems available for tests than tests to execute, we can achieve total coverage in less iterations by repeating tests from earlier groups.

Trade-offs in my trade-offs

Even this makes some significant assumptions:
  1. Each item we are packing into a bin is of equal size. This means every test is assumed to run in the same amount of time on the same computer.
  2. Each item is indivisible
  3. Each bin values each item equally (in our context this means "every computer executes it in the same amount of time")
  4. Each test will never change in how long it takes to execute when it changes, or the system under test does.
  5. Each bin represents one computer only.

Obviously the only realistic assumption here is #2. If tests can be executed faster by breaking them into smaller tests, the test authors should do so, not an argument builder.

Assumptions #1 and #3, if we take them seriously would not only doom us to solving an NP hard problem, but have a host of other practical issues. Knowing how long each test takes on each computer is quite a large sampling problem, though solvable eventually even using only git tags to store this data. Even then, #4 makes this an exercise in futility. We really have no choice but to accept this source of inefficiency in our production process.

Invalidating #5 does not bring us too much trouble. Since we expect to have a number of test hosts which will satisfy any given configuration from the optimal group and will know how many there are ahead of time, we can simply split the bin over the available hosts and re-run our bin packer over those hosts.

This will inevitably result in a situation where you have an overabundance of available systems under test for some configurations and a shortage of others. Given enough tests, this can result in workflow disruptions. This is a hard problem to solve without "throwing money at the problem", or being more judicious with what configurations you support in the first place. That is the sort of problem an organization wants to have though. It is preferable to the problem of wasting money testing everything on every configuration.

Whither N-wise

Since the name of the tool is pairwise, I may as well also implement and discuss multi-set combinations.  Building these bins is actually quite straightforward, which is somewhat shocking given every algorithm featured for doing pairwise testing at pairwise.org was not in fact the optimal one from my 30 year old combinatorics textbook.  Pretty much all of them used tail-call recursion in languages which do not optimize this, or they took (good) shortcuts which prevented them from functioning in N dimensions.

Essentially you build an iterator which, starting with the first set, pushes a partial combination with every element of its set matched with one of the second onto your stack.
You then repeat the process, considering the first set to be the partial, and crank right through all the remaining sets.

Dealing with incompatibilities is essentially the same procedure as above.  The completed algorithm looks like so:

sub combine($conf,$tests) {
    my %pgroups = ref $conf->{PlatformGroups} eq 'HASH' ? %{$conf->{PlatformGroups}} : ();
    my @plans;

    #construct iterator
    my @pigeonholes = values(%pgroups);
    my $bins = product map { scalar(@$_) } @pigeonholes;
    my $tot_tests = scalar(@$tests);

    # Bin covering
    my $remainder = $tot_tests % $bins;
    my $to_take = int$tot_tests / $bins);

    my $offset = 0;

    my @iterator = @{$pigeonholes[0]};
    while (scalar(@iterator) ) {
        my $subj = shift @iterator;

        #Handle initial elements
        $subj = [$subjif ref $subj ne 'ARRAY';

        #Break out of the loop if we have no more possibilities to exploit
        if (scalar(@$subj) == scalar(@pigeonholes)) {
            my $tt = $to_take + ( $remainder && 1 ) || 1;
            push(@plans, { tests => [ $offset$tt ], platforms => $subj } );
            $remainder-- if $remainder;
            $offset += $tt;
            # Just repeat tests in the event we have more SUTs than tests
            $offset = $offset % $tot_tests;
            next;
        }

        #Keep pushing partials on to the end of the iterator, until we run out of categories to add
        foreach my $element (@{$pigeonholes[scalar(@$subj)]}) {
            my @partial = @$subj;
            # If the combination isn't valid, return an undef member to simplify loop breakout
            # This results in some configurations which are essentially the same.
            # That said, we cannot simply discard them if we wish to cover the case a configuration having incompatibilities with entire configuration groups.
            # We could compress them later to avoid some slop, but it's probably not worth the effort.
            push(@partial, combination_valid($conf,@partial) ? $element : undef );
            push(@iterator,\@partial);
        }
    }
    return \@plans;
}

Uniting all under Heaven

You may have noticed this is a greedy algorithm.  If we decided to use this as a way to generate a cache for a "god algorithm" version of the anti-clique generator above, we could very easily run into memory exhaustion with large enough configuration sets, defeating the purpose. You could flush the partials that are actually complete, but even then you'd only be down to 1/n theoretical memory usage where n is the size of your 2nd largest configuration set (supposing you sort such that it's encountered last).  This may prove "good enough" in practice, especially since users tend to tolerate delays in the "node added to network" phase better than the "trying to run tests" phase.  It would also speed up the matching of available systems under test to the desired configuration supersets, as we could also "already know the answer".

Profiling this showed that I either had to fix my algorithm or resort to this.  My "worst case" example of 100 million tests using the cliques() method took 3s, while generating everything took 4.  Profiling shows the inefficient parts are almost 100% my bin-covering.

Almost all of this time is spent splice()ing huge arrays of tests.  In fact, the vast majority of the time in my test (20s total!) is simply building the sequence (1..100_000_000), which we are using as a substitute for a similar length argument array of tests.

We are in luck, as once again we have an optimization suggested by the constraints of our execution environment.  Given any host only needs to know what it needs to execute we can save only the relevant indices, and do lazy evaluation.  This means our sequence expansion (which takes the most time) has an upper bound of how long it takes to generate up to our offset.  The change is straightforward:

push(@plans,{ tests => [ $offset$tt ], platforms => \@newplats });

The question is, can we cheat even more by starting at our offset too?  Given we are expecting a glob or regex describing a number of files which we don't know ahead of time what will be produced, this seems unlikely.  We could probably speed it up globbing with GLOB_NOSORT. Practically every other sieve trick we can try (see DeMorgan's Laws) is already part of the C library implementing glob itself.  I suspect that we will have to understand the parity problem a great deal better for optimal seeking via search criteria.

Nevertheless, this gets our execution time for the cliques() algorithm down to 10ms, and 3s as the upper bound to generate our sequence isn't bad compared to how long it will take to execute our subset of 100 million tests.  We'd probably slow the program down using a cached solution at this point, not to mention having to deal with the problems inherent with such.  Generating all combinations as we'd have to do to build the cache itself takes another 3s, and there's no reason to punish most users just to handle truly extreme data sets.

It is possible we could optimize our check that a combination is valid, and get a more reasonable execution time for combine() as well.  Here's our routine as a refresher:

sub combination_valid ($conf,@combo) {
    my %compat = %{$conf->{incompatibilities}};
    foreach my $key (keys(%compat)) {
        next unless ref $compat{$keyeq 'ARRAY';
        my @compat = grep { my $element = $_defined $element && ( $element eq $key || grep { $element eq $_ } @{$compat{$key}} ) } @combo;
        return 0 if @compat > 1;
    }
    return 1;
}

Making the inner grep a List::Util::first instead seems obvious, but the added overhead made it not worth it for the small data set. Removing our guard on the other hand halved execution time, so I have removed it in production.  Who knew ref( ) was so slow?  Next, I "disengaged safety protocols" by turning off warnings and killing the defined check.  This made no appreciable difference, so I still haven't yet run into a situation where I've needed to turn off warnings in a tight loop.  Removing the unnecessary allocation of @compat and returning directly shaved another 200ms.  All told, I got down to 800ms, which is in "detectable but barely" delay territory, which is good enough in my book.

Conclusion

The thing I take away from all this is that the most useful thing a mathematics education teaches is the ability to identify specific problems as instances of generalized problems (to which a great deal of thinking has already been devoted).  While this is not a new lesson, I continuously astonish myself how unreasonably effective it is.  That, and exposure to the wide variety of pursuits in mathematics gives a leg up as to where to start looking.

I also think the model I took developing this has real strength.  Developing a program while simultaneously doing what amounts to a term paper on how it's to operate very clearly draws out the constraints and acceptance criteria from a program in an apriori way.  It also makes documentation a fait accompli.  Making sure to test and profile while doing this as well completed the (as best as is possible without users) methodologically dual design, giving me the utmost confidence that this program will be fit for purpose.  Given most "technical debt" is caused by not fully understanding the problem when going into writing your program (which is so common it might shock the uninitiated) and making sub-optimal trade-offs when designing it, I think this approach mitigates most risks in that regard.

That said, it's a lot harder to think things through and then test your hypotheses than just charging in like a bull in a china shop or groping in the dark.  This is the most common pattern I see in practice doing software development professionally.  To be fair, it's not like people are actually willing to pay for what it takes to achieve real quality, and "good enough" often is.  Bounded rationality is the rule of the day, and our lot in life is mostly that of a satisficer.  Optimal can be the enemy of good, and the tradeoffs we've made here certainly prove this out.

When I was doing QA for a living people are surprised when I tell them the most important book for testers to read is Administrative Behavior. This is because you have to understand the constraints of your environment do do your job well, which is to provide actionable information to decision-makers.  I'm beginning to realize this actually suffuses the entire development process from top to bottom.


25 most recent posts older than 1618336942
Size:
Jump to:
POTZREBIE
© 2020-2023 Troglodyne LLC