Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
46
When I build and release new features or bug fixes for RestAssured.Net, I rely heavily on the acceptance tests that I wrote over time. Next to serving as living documentation for the library, I run these tests both locally and on every push to GitHub to see if I didn’t accidentally break something, for different versions of .NET. But how reliable are these tests really? Can I trust them to pass and fail when they should? Did I cover all the things that are important? I speak, write and teach about the importance of testing your tests on a regular basis, so it makes sense to start walking the talk and get more insight into the quality of the RestAssured.Net test suite. One approach to learning more about the quality of your tests is through a technique called mutation testing. I speak about and demo testing your tests and using mutation testing to do so on a regular basis (you can watch a recent talk here), but until now, I’ve pretty much exclusively used PITest for Java. As...
5 months ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from On Test Automation

Building and improving Page Objects, one step at a time

A few weeks ago, I ran a pair programming / mentoring session with someone who reached out to me because they felt they could use some support. When I first saw the code they wrote, I was pretty impressed. Sure, there were some things I would have done differently, but most of that was personal preference, not a matter of my way being better than their way objectively. Instead of working on their code directly, instead, we therefore decided to build up some test code together from zero, discussing and applying good programming principles and patterns along the way. As the tests were using Playwright in TypeScript, and were heavily oriented towards using the graphical user interface, we decided to start building a Page Object-based structure for a key component in their application. This component was a UI component that enabled an end user to create a report in the system. The exact type of system or even the domain itself isn’t really important for the purpose of this blog post, by the way. The component looked somewhat like this, heavily simplified: At the top, there was a radiobutton with three options that selected different report layouts. Every report layout consists of multiple form fields, and most form fields are text areas plus lock buttons that open a dropdown-like structure where you can edit the permissions for that field by selecting one or more roles that can view the contents of that text field (this is a privacy feature). And of course, there’s a save button to save the report, as well as a print button. The actual UI component had a few other types of components, but for the sake of brevity, let’s stick to these for now. Iteration 0 - creating an initial Page Object My approach whenever I start from scratch, either on my own or when working with someone else, is to take small steps and gradually introduce complexity. It might be tempting to immediately create a Page Object containing fields for all the elements and methods to interact with them, but that is going to get messy very quickly. Instead, we started with the simplest Page Object we could think of: one that allowed us to create a standard report, without considering the lock buttons to set permissions. Let’s assume that a standard report consists of only a title and a summary text field. The first iteration of that Page Object turned out to look something like this: export class StandardReportPage { readonly page: Page; readonly radioSelectStandard: Locator; readonly textfieldTitle: Locator; readonly textfieldSummary: Locator; readonly buttonSaveReport: Locator; readonly buttonPrintReport: Locator; constructor(page: Page) { this.page = page; this.radioSelectStandard = page.getByLabel('Standard report'); this.textfieldTitle = page.getByPlaceholder('Title'); this.textfieldSummary = page.getByPlaceholder('Summary'); this.buttonSaveReport = page.getByRole('button', { name: 'Save' }); this.buttonPrintReport = page.getByRole('button', { name: 'Print' }); } async select() { await this.radioSelectStandard.click(); } async setTitle(title: string) { await this.textfieldTitle.fill(title); } async setSummary(summary: string) { await this.textfieldSummary.fill(summary); } async save() { await this.buttonSaveReport.click(); } async print() { await this.buttonPrintReport.click(); } } which makes the test using this Page Object look like this: test('Creating a standard report', async ({ page } ) => { const standardReportPage = new StandardReportPage(page); await standardReportPage.select(); await standardReportPage.setTitle('My new report title'); await standardReportPage.setSummary('Summary of the report'); await standardReportPage.save(); await expect(page.getByTestId('standard-report-save-success')).toBeVisible(); }); Iteration 1 - grouping element interactions My first question after we implemented and used this Page Object was: ‘how do you feel about the readability of this test?’. Of course, we just wrote this code, and it’s a small example, but imagine you’re working with Page Objects that are all written like this, and offer many more element interactions. This will quickly lead to very procedural test code ‘enter this, enter that, click here, check there’ that doesn’t show the intent of the test very clearly. In other words, this coding style does not really do a great job of hiding the implementation of the page (even when it hides the locators) and focusing only on behaviour. To improve this, I suggested grouping element interactions that form a logical end user interaction together in a single method and expose that. When I read or write a test, I’m not particularly interested in the sequence of individual element interactions I need to execute to perform a higher-level action. I’m not interested in ‘filling a text field’ or ‘clicking a button’, I’m interested in ‘creating a standard report’. This led us to refactor the Page Object into this: export class StandardReportPage { readonly page: Page; readonly radioSelectStandard: Locator; readonly textfieldTitle: Locator; readonly textfieldSummary: Locator; readonly buttonSaveReport: Locator; readonly buttonPrintReport: Locator; constructor(page: Page) { this.page = page; this.radioSelectStandard = page.getByLabel('Standard report'); this.textfieldTitle = page.getByPlaceholder('Title'); this.textfieldSummary = page.getByPlaceholder('Summary'); this.buttonSaveReport = page.getByRole('button', { name: 'Save' }); this.buttonPrintReport = page.getByRole('button', { name: 'Print' }); } async select() { await this.radioSelectStandard.click(); } async create(title: string, summary: string) { await this.textfieldTitle.fill(title); await this.textfieldSummary.fill(summary); await this.buttonSaveReport.click(); } async print() { await this.buttonPrintReport.click(); } } which in turn made the test look like this: test('Creating a standard report', async ({ page } ) => { const standardReportPage = new StandardReportPage(page); await standardReportPage.select(); await standardReportPage.create('My new report title', 'Summary of the report'); await expect(page.getByTestId('standard-report-save-success')).toBeVisible(); }); Much better already when it comes to readability and ‘expose behaviour, hide implementation’. Doing exactly this is not something that is unique to UI automation, or even to test automation in general, by the way. This principle is called encapsulation, and it is one of the fundamental principles of object-oriented programming. It is a principle that is very useful to know when you’re writing test code, if you want to keep your code readable, that is. Iteration 2 - Adding the ability to set permissions on a form field For our next step, we decided to introduce the ability to set the access permissions for every text field. As explained and shown in the graphical representation of the form at the top of this post, every form field in the standard form has an associated lock button that opens a small dialog where the user can select which user roles can and cannot see the report field. Our initial idea was to simply add additional fields in the Page Object representing the standard report. However, that would lead to a lot of repetitive work and to the standard report having a lot of fields containing element locators. So, we decided to see if we could consider the combination of a report text field and the associated permission lock button as a Page Component, i.e., a separate class that encapsulates the behaviour of a group of related elements on a specific page. Setting this up in a reusable manner will be a lot easier when the HTML for these Page Components has the same structure across the entire application. The good news is that this is often the case, especially when frontend designers and developers design and implement frontends using tools like Storybook. So, the relevant part of the HTML for the standard form might look like this (again, simplified): <div id="standard_form"> <div data-testid="form_field_subject"> <div data-testid="form_field_subject_textfield"></div> <div data-testid="form_field_subject_lock"></div> </div> <div data-testid="form_field_summary"> <div data-testid="form_field_summary_textfield"></div> <div data-testid="form_field_summary_lock"></div> </div> </div> An example reusable Page Component class might then look something like this: export class ReportFormField { readonly page: Page; readonly textfield: Locator; readonly buttonLockPermissions: Locator; constructor(page: Page, formFieldName: string) { this.page = page; this.textfield = page.getByTestId(`${formFieldName}_textfield`); this.buttonLockPermissions = page.getByTestId(`${formFieldName}_lock`); } async complete(text: string, roles: string[]) { await this.textfield.fill(text); await this.buttonLockPermissions.click(); // handle setting permissions for the form field } } Note how the constructor of this Page Component class uses (in fact, relies on) the predictable, repetitive structure of the component in the application and the presence of data-testid attributes. If your components do not have these, find a way to add them, or find another generic way to locate the individual elements in the component on the page. Now that we have defined our Page Component class, we need to define the relationship between these Page Components and the Page Object that contains them. In the past, my choice would default to creating base Page classes that would contain the reusable Page Components, as well as other utility methods. The more specific Page Object would then inherit from these base Pages, allowing them to use the methods defined in the parent base Page class. Almost invariably, at some point that would lead to very messy base Page classes, with lots of fields and methods in it that were only tangentially related, at best. The cause of this mess? Me not thinking clearly about the type of the relation between different Page Objects and Components. You see, creating base classes and using inheritance for reusability creates ‘is-a’ relations. These are useful when the relation between objects is of an ‘is-a’ nature. However, in our case, there is no ‘is-a’ relation, there is a ‘has-a’ relation. A Page Object has a certain Page Component. In other words, we need to define the relationship a different way, and that’s by using composition instead of inheritance. We define Page Components as components of our Page Objects, which makes for a far more natural relationship between the two, and for code that is way more clearly structured: export class StandardReportPage { readonly page: Page; readonly radioSelectStandard: Locator; readonly reportFormFieldTitle: ReportFormField; readonly reportFormFieldSummary: ReportFormField; readonly buttonSaveReport: Locator; readonly buttonPrintReport: Locator; constructor(page: Page) { this.page = page; this.radioSelectStandard = page.getByLabel('Standard report'); this.reportFormFieldTitle = new ReportFormField(this.page, 'title'); this.reportFormFieldSummary = new ReportFormField(this.page, 'summary'); this.buttonSaveReport = page.getByRole('button', { name: 'Save' }); this.buttonPrintReport = page.getByRole('button', { name: 'Print' }); } async select() { await this.radioSelectStandard.click(); } async create(title: string, summary: string, roles: string[]) { await this.reportFormFieldTitle.complete(title, roles); await this.reportFormFieldSummary.complete(summary, roles); await this.buttonSaveReport.click(); } async print() { await this.buttonPrintReport.click(); } } Reading this code feels far more natural than cramming everything into one or more parent classes c.q. base page objects. Lesson learned here: the way objects are related in your code should reflect the relationship between these objects in real life, that is, in your application. Iteration 3 - What about the other report types? The development and refactoring steps we have gone through so far led us to a point where we were pretty happy with the code. However, we still only have Page Objects for a single type of form, and as you have seen in the sketch at the top of this blog post, there are different types of forms. How do we deal with those? Especially when we know that these forms share some components and behaviour, but not all of them? It is tempting to immediately jump to conclusions and start throwing patterns and structures at the problem, but in pair programming sessions like this, I typically try and avoid finding and implementing the ‘final’ solution right away. Why? Because the best learning is done when you see (or create, in this case) a suboptimal situation, discuss the problems with that situation, investigate potential solutions and only then implement them. Sure, it will take longer, initially, but this is made up for in spades with a much better understanding of what suboptimal code looks like and how to improve it. So, first we create separate classes for individual report types, each similar to the implementation for the standard report we created before. Here is an example for an extended report, containing more form fields (well, just one more, but you get the idea): export class ExtendedReportPage { readonly page: Page; readonly radioSelectExtended: Locator; readonly reportFormFieldTitle: ReportFormField; readonly reportFormFieldSummary: ReportFormField; readonly reportFormFieldAdditionalInfo: ReportFormField; readonly buttonSaveReport: Locator; readonly buttonPrintReport: Locator; constructor(page: Page) { this.page = page; this.radioSelectExtended = page.getByLabel('Extended report'); this.reportFormFieldTitle = new ReportFormField(this.page, 'title'); this.reportFormFieldSummary = new ReportFormField(this.page, 'summary'); this.reportFormFieldAdditionalInfo = new ReportFormField(this.page, 'additionalInfo'); this.buttonSaveReport = page.getByRole('button', { name: 'Save' }); this.buttonPrintReport = page.getByRole('button', { name: 'Print' }); } async select() { await this.radioSelectExtended.click(); } async create(title: string, summary: string, additionalInfo: string, roles: string[]) { await this.reportFormFieldTitle.complete(title, roles); await this.reportFormFieldSummary.complete(summary, roles); await this.reportFormFieldAdditionalInfo.complete(additionalInfo, roles); await this.buttonSaveReport.click(); } async print() { await this.buttonPrintReport.click(); } } Obviously, there’s a good amount of duplication between this class and the Page Object for the standard report. What to do with them? Contrary to the situation with the Page Components, it is a good idea to reduce the duplication by creating a base report Page Object here. We’re talking about creating an ‘is-a’ relationship (inheritance) here, not a ‘has-a’ relation (composition). A standard report is a report. That means that in this case, we can, and we should, create a base report Page Object, move some (or maybe even all) of the duplicated code there, and have the specific report Page Objects derive from that base report class. My recommendation here is to make the base report Page Object an abstract class to prevent people from instantiating it directly. This leads to more expressive and clear code, as we can only instantiate the concrete report subtype, which will make it immediately clear to the reader of the code what type of report they’re dealing with. In the abstract class, we declare the elements that are shared between all reports. This applies to methods, but also to web elements that appear in all report types. This is what the abstract base class might look like: export abstract class ReportBasePage { readonly page: Page; readonly reportFormFieldTitle: ReportFormField; readonly reportFormFieldSummary: ReportFormField; readonly buttonSaveReport: Locator; readonly buttonPrintReport: Locator; abstract readonly radioSelect: Locator; protected constructor(page: Page) { this.page = page; this.reportFormFieldTitle = new ReportFormField(this.page, 'title'); this.reportFormFieldSummary = new ReportFormField(this.page, 'summary'); this.buttonSaveReport = page.getByRole('button', { name: 'Save' }); this.buttonPrintReport = page.getByRole('button', { name: 'Print' }); } async select() { await this.radioSelect.click(); } async print() { await this.buttonPrintReport.click(); } } and a concrete class for the standard report, implementing the abstract class now looks like this: export class ExtendedReportPage extends ReportBasePage { readonly page: Page; readonly radioSelect: Locator; readonly reportFormFieldAdditionalInfo: ReportFormField; constructor(page: Page) { super(page); this.page = page; this.radioSelect = page.getByLabel('Extended report'); this.reportFormFieldAdditionalInfo = new ReportFormField(this.page, 'additionalInfo'); } async create(title: string, summary: string, additionalInfo: string, roles: string[]) { await this.reportFormFieldTitle.complete(title, roles); await this.reportFormFieldSummary.complete(summary, roles); await this.reportFormFieldAdditionalInfo.complete(additionalInfo, roles); await this.buttonSaveReport.click(); } } The abstract class takes care of the methods that are shared between all reports, such as the print() and the select() methods, It also defines what elements and methods should be implemented by the implementing concrete classes. For now, that’s only the radioSelect locator. Note that at the moment, because the data required for the different types of reports is not the same, we cannot yet add an abstract select(): void method requirement, that all report Page Objects should implement, to our abstract class. This is a temporary drawback and one that we will address in a moment. Also note that the test code doesn’t change, but we can now create both a standard report and an extended report that, behind the scenes, share a significant amount of code. Definitely a step in the right direction. Iteration 4 - Dealing with test data Our tests already look pretty good. They are easy to read, and the way the code is structured aligns with the structure of the parts of the application they’re representing. Are we done yet? Well, maybe. As a final improvement to our tests, let’s have a look at the way we handle our test data. Right now, the test data we use in our test methods is simply an unstructured collection of strings, integers, boolean and so on. For small tests and a simple domain that is easy to understand, you might get away with this, but as soon as your test suite grows and your domain becomes more complex, this will get confusing. What does that string value represent exactly? Why is that variable a boolean and what happens if it is set to true (or false)? This is where test data objects can help out. Test data objects are simple classes, often nothing more fancy than a Data Transfer Object (DTO), that represent a domain entity. In this situation, that domain entity might be a report, for example. Having types that represent domain entities greatly improves the readability of our code, it will make it much easier to understand what exactly we’re doing here. The implementation of these test data objects is often straightforward. In TypeScript, we can use a simple interface for this purpose. I chose to create one ReportContent class that contains the data for all of our report types. As they diverge, I might choose to refactor these into separate interfaces, but for now, this is fine. Also, defining this test data object has the additional benefit of allowing me to move the definition of the create() method for the different report Page Objects to the abstract base class, a step that we were unable to perform previously. This is what my interface looks like: export interface ReportContent { title: string; summary: string; additionalInfo?: string; roles: string[]; } The additionalInfo field is marked as optional, as it only appears in an extended report, not in a standard report. In some cases, to further improve flexibility and readability of our code, we might add a builder or a factory that helps us create instances of our test data objects using a fluent syntax. This also allows us to set sensible default values for properties to avoid having to assign the same values to these properties in every test. In this specific case, that’s not really necessary, because object creation based on an interface in TypeScript is really straightforward, and our ReportContent object is small, anyway. Your mileage may vary. Now that we have defined a type for our report data, we can change the signature and the implementation of the create() methods in our Page Objects to use this type. Here’s an example for the extended report: async create(report: ReportContent) { await this.reportFormFieldTitle.complete(report.title, report.roles); await this.reportFormFieldSummary.complete(report.summary, report.roles); await this.reportFormFieldAdditionalInfo.complete(report.additionalInfo, report.roles); await this.buttonSaveReport.click(); } and we can now add the following line to the abstract ReportBasePage class: abstract create(report: ReportContent): void; to enforce all report Page Objects to implement a create() method that takes an argument of type ReportContent. We can do the same for other test data objects. Oh, and if you’re storing your tests in the same repository as your application code, these test data objects might even exist already, in which case you might be able to reuse them. This is definitely worth checking, because why would we reinvent the wheel? That was a lot of work, but it has led to code that is, in my opinion, well-structured and easy to read and maintain. As this blog post has hopefully shown, it is very useful to have a good working knowledge of common object-oriented programming principles and patterns when you’re writing test code. This is especially true for UI automation, but many of the principles we have seen in this blog post can be applied to other types of test automation, too. There are many other patterns out there to explore. This blog post is not an attempt to list them all, nor does it show ‘the one true way’ of writing Page Objects. Hopefully, though, it has shown you my thought process when I write test automation code, and how understanding fundamentals of object-oriented programming helps me do this better. A massive ‘thank you’ to Olena for participating in the pair programming session I discussed and for reviewing this blog post. I really appreciate it.

3 months ago 31 votes
My terms and conditions for using AI

Last weekend, I wrote a more or less casual post on LinkedIn containing the ‘rules’ (it’s more of a list of terms and conditions, really) I set for myself when it comes to using AI. That post received some interesting comments that made me think and refine my thoughts on when (not) to use AI to support me in my work. Thank you to all of you who commented for doing so, and for showing me that there still is value in being active on LinkedIn in between all the AI-generated ‘content’. I really appreciate it. Now, AI and LLMs like ChatGPT or Claude can be very useful, that is, when used prudently. I think it is very important to be conscious and cautious when it comes to using AI, though, which is why I wrote that post. I wrote it mostly for myself, to structure my thoughts around AI, but also because I think it is important that others are at least conscious of what they’re doing and working with. That doesn’t mean you have to adhere to or even agree with my views and the way I use these tools, by the way. Different strokes for different folks. Because of the ephemeral nature of these LinkedIn posts, and the importance of the topic to me, I want to repeat the ‘rules’ (again, more of a T&C list) I wrote down here. This is the original, unchanged list from the post I wrote on February 15: I only use it to support me in completing tasks I understand. I need to be able to scrutinize the output the AI system produces and see if it is both sound and fit for the purpose I want to use it for. I never use it to explain to me something I don’t know yet or don’t understand enough. I have seen and read about too many hallucinations to trust them to teach me what I don’t understand. Instead, I use books, articles, and other content from authors and sources I do trust if I’m looking to learn something new. I never EVER use it for creative work. I don’t use AI-generated images anywhere, and all of my blogs, LinkedIn posts, comments, course material and other written text are 100% my own, warts and all. My views, my ideas, my voice. Interestingly, most of the comments were written in reaction to the first two bullet points at the time I wrote this blog post. I don’t know exactly why this is the case, it might be because the people who read it agree (which I doubt seeing the tsunami of AI-generated content that’s around these days), or maybe because there’s a bit of stigma around admitting to use AI for content generation. I don’t know. What I do know is that it is an important principle to me. I wrote about the reasons for that in an earlier blog post, so I won’t repeat myself here. Like so many terms and conditions, the list I wrote down in this post will probably evolve over time, but what will not change is me remaining very careful around where I use and where I don’t use AI to help me in my work. Especially now that the speed with which new developments in the AI space are presented to us and the claims around what it can and will do only get bigger, I think it is wise to remain cautious and look at these developments with a critical and very much human view.

5 months ago 36 votes
On working and contributing to conferences abroad

This blog post is another one in the ‘writing things down to structure my thinking on where I want my career to go’ series. I will get back to writing technical and automation blog posts soon, but I need to finish my contract testing course first. One of the things I like to do most in life is traveling and seeing new places. Well, seeing new places, mostly, as the novelty of waiting, flying and staying in hotel rooms has definitely worn off by now. I am in the privileged position (really, that is what it is: I’m privileged, and I fully realize that) that I get to scratch this travel itch professionally on a regular basis these days. Over the last few years, I have been invited to contribute to meetups and conferences abroad, and I also get to run in-house training sessions with companies outside the Netherlands a couple of times per year. Most of this traveling takes place within Europe, but for the last three years, I have been able to travel outside of Europe once every year (South Africa in 2022, Canada in 2023 and the United States in 2024), and needless to say I have enjoyed those opportunities very much. To give you an idea of the amount of traveling I do: for 2025, I now have four work-related trips abroad scheduled, and I am pretty sure at least a few more will be added to that before the year ends (it’s only just February…). That might not be much travel by some people’s standards, but for me, it is. And it seems the number of opportunities I get for traveling increase year over year, to the point where I have to say ‘no’ to several of these opportunities. Say no? Why? I thought you just said you loved to travel? Yes, that’s true. I do love to travel. But I also love spending time at home with my family, and that comes first. Always. Now, my sons are getting older, and being away from home for a few days doesn’t put as much pressure on them and on my wife as it did a few years ago. Still, I always need to find a balance between spending time with them and spending time at work. I am away from home for work not just when I’m abroad. I run evening training sessions with clients here in the Netherlands on a regular basis, too, as well as training sessions in my evenings for clients in different time zones, mainly US-based clients. And all that adds up. I try to only be away from home one night per week, but often, it’s two. When I travel abroad, it’s even more than that. Again, I’m not complaining. Not at all. It is an absolute privilege to get to travel for work and get paid to do that, but I cannot do that indefinitely, and that’s why I have made a decision: With a few exceptions (more on those below), I am going to say ‘no’ to conferences abroad from now on. This is a tough decision for me to make, but sometimes that’s exactly what you need to do. Tough, because I have very fond memories of all the conferences and meetups abroad I have contributed to. My first one, Romanian Testing Conference in 2017. My first keynote abroad, UKStar in 2019. My first one outside of Europe, Targeting Quality in 2023. They were all amazing, because of the travel and sightseeing (when time allowed), but also because of all the people I have met at these conferences. Yet, I can meet at least some of these people at conferences here in the Netherlands, too. Test Automation Days, the TestNet events, the Dutch Testing Day and TestMass all provide a great opportunity for me to catch up with my network. Sometimes, international conferences come to the Netherlands, too, like AutomationSTAR this year. And then there are plenty of smaller meetups here in the Netherlands (and Belgium) where I can meet and catch up with people as well. Plus, the money. I am not going to be a hypocrite and say that money doesn’t play into this. For the reasons mentioned above, I have a limited number of opportunities to travel every year, and I prefer to spend those on in-house training sessions with clients abroad, simply because the pay is much better. Even when a conference compensates flights and hotel (as they should) and offer a speaker or workshop facilitator fee (a nice bonus), it will be significantly less of a payday than when I run a training session with a client. That’s not the fault of those conferences, not at all, especially when they’re compensating their speakers fairly, but this is simply a matter of numbers and budgets. At the moment, I have one, maybe two contributions to conferences abroad coming up, and I gave them my word, so I’ll be there. That’s the SAST 30-year anniversary conference in October, plus one other conference that I’m talking to but haven’t received a ‘yes’ or ‘no’ from yet. Other than that, if conferences reach out to me, it’s likely to be a ‘no’ from now on, unless: the event pays a fee comparable to my rate for in-house training I can combine the event with paid in-house training (for example with a sponsor) it is a country or region I really, really want to visit, either for personal reasons or because I want to grow my professional network there I don’t see the first one happening soon, and the list of destinations for the third one is very short (Norway, Canada, New Zealand, that’s pretty much it), so unless we can arrange paid in-house training alongside the conference, the answer will be a ‘no’ from me. Will this reduce the number of travel opportunities for me? Maybe. Maybe not. Again, I see the number of requests I get for in-house training abroad growing, too, and if that dies down, it’ll be a sign for me that I’ll have to work harder to create those opportunities. For 2025, things are looking pretty good, with trips for training to Romania, North Macedonia and Denmark already scheduled, and several leads for more in the pipeline. And if the number of opportunities does go down, that’s fine, too. I’m happy to spend that time with family, working on other things, or riding my bike. And I’m sure there will be a few opportunities to speak at online meetups, events and webinars, too.

5 months ago 50 votes
My career and a thought experiment

As is the case every year, 2025 is starting off relatively slowly. There’s not a lot of training courses to run yet, and since a few of the projects I worked on wrapped up in December, I find myself with a little bit of extra time and headspace on my hands. I actually enjoy these slower moments, because they give me some time to think about where my professional career is going, if I’m still happy with the direction it is going on, and what I would like to see changed. Last year, I quit doing full time projects as an individual contributor to development teams in favour of part-time consultancy work and more focus on my training services. 2024 has been a great year overall, and I would be happy to continue working in this way in 2025. However, as a thought experiment, I took some time to think about what it would take for me to go back to full time roles, or maybe (maybe!) even consider joining a company on a permanent basis. Please note that this post is not intended as an ‘I need a job!’ cry for help. My pipeline for 2025 is slowly but surely filling up, and again, I am very happy with the direction my career is going at the moment. However, I have learned that it never hurts to leave your options open, and even though I love the variety in my working days these days, I think I would enjoy working with one team, on one goal, for an extended amount of time, too, under the right conditions. If nothing else, this post might serve as a reference post to send to people and companies that reach out to me with a full time contract opportunity or even a permanent job opening. This is also not a list of requirements that is set in stone. As my views on what would make a great job change (and they will), I will update this post to reflect those views. So, to even consider joining a company on a full-time contract or even a permanent basis, there are basically three things I will and should consider: What does the job look like? What will I be doing on a day-to-day basis? What are the must-haves regarding terms and conditions? What are the nice to haves that would provide the icing on the cake for me? Let’s take a closer look at each of these things. What I look for in a job As I mentioned before, I am not looking for a job as an individual contributor to a development team. I have done that for many years, and it does not really give me the energy that it used to. On the other hand, I am definitely not looking for a hands-off, managerial kind of role, as I’d like to think I would make an atrocious manager. Plus, I simply enjoy being hands-on and writing code way too much to let that go. I would like to be responsible for designing and implementing the testing and automation strategy for a product I believe in. It would be a lead role, but, as mentioned, with plenty (as in daily) opportunities to get hands-on and contribute to the code. The work would have to be technically and mentally challenging enough to keep me motivated in the long term. Getting bored quickly is something I suffer from, which is the main driver behind only doing part-time projects and working on multiple different things in parallel right now. I don’t want to work for a consultancy and be ‘farmed out’ to their clients. I’ve done that pretty much my entire career, and if that’s what the job will look like, I’d rather keep working the way I’m working now. The must-haves There are (quite) a few things that are non-negotiable for me to even consider joining a company full time, no matter if it’s on a contract or a permanent basis. The pay must be excellent. Let’s not beat around the bush here: people work to make money. I do, too. I’m doing very well right now, and I don’t want that to change. The company should be output-focused, as in they don’t care when I work, how many hours I put in and where I work from, as long as the job gets done. I am sort of spoiled by my current way of working, I fully realise that, but I’ve grown to love the flexibility. By the way, please don’t read ‘flexible’ as ‘working willy-nilly’. Most work is not done in a vacuum, and you will have to coordinate with others. The key word here is ‘balance’. Collaboration should be part of the company culture. I enjoy working in pair programming and pair testing setups. What I do not like are pointless meetings, and that includes having Scrum ceremonies ‘just because’. The company should be a remote-first company. I don’t mind the occasional office day, but I value my time too much to spend hours per week on commuting. I’ve done that for years, and it is time I’ll never get back. The company should actively stimulate me contributing to conferences and meetups. Public speaking is an important part of my career at the moment, and I get a lot of value from it. I don’t want to give that up. There should be plenty of opportunities for teaching others. This is what I do for a living right now, I really enjoy it, and I’d like to think I’m pretty good at it, too. Just like with the public speaking, I don’t want to give that up. This teaching can take many forms, though. Running workshops and regular pairing with others are just two examples. The job should scratch my travel itch. I travel abroad for work on average about 5-6 times per year these days, and I would like to keep doing that, as I get a lot of energy from seeing different places and meeting people. Please note that ‘traveling’ and ‘commuting’ are two completely different things. Yes, I realize this is quite a long list, but I really enjoy my career at the moment, and there are a lot of aspects to it that I’m not ready to give up. The nice to haves There are also some things that are not strictly necessary, but would be very nice to have in a job or full time contract: The opportunity to continue working on side gigs. I have a few returning customers that I’ve been working with for years, and I would really appreciate the opportunity to continue doing that. I realise that I would have to give up some things, but there are a few clients that I would really like to keep working with. By the way, this is only a nice to have for permanent jobs. For contracting gigs, it is a must-have. It would be very nice if the technology stack that the company is using is based on C#. I’ve been doing quite a bit of work in this stack over the years and I would like to go even deeper. If the travel itch I mentioned under the must-haves could be scratched with regular travel to Canada, Norway or South Africa, three of my favourite destinations in the world, that would be a very big plus. I realize that the list of requirements above is a long one. I don’t think there is a single job out there that ticks all the boxes. But, again, I really like what I’m doing at the moment, and most of the boxes are ticked at the moment. I would absolutely consider going full time with a client or even an employer, but I want it to be a step forward, not a step back. After all, this is mostly a thought experiment at the moment, and until that perfect contract or job comes along, I’ll happily continue what I’m doing right now.

6 months ago 63 votes

More in programming

Increase software sales by 50% or more

This is re-post of How to Permanently Increase Your Sales by 50% or More in Only One Day article by Steve Pavlina Of all the things you can do to increase your sales, one of the highest leverage activities is attempting to increase your products’ registration rate. Increasing your registration rate from 1.0% to 1.5% means that you simply convince one more downloader out of every 200 to make the decision to buy. Yet that same tiny increase will literally increase your sales by a full 50%. If you’re one of those developers who simply slapped the ubiquitous 30-day trial incentive on your shareware products without going any further than that, then I think a 50% increase in your registration rate is a very attainable goal you can achieve if you spend just one full day of concentrated effort on improving your product’s ability to sell. My hope is that this article will get you off to a good start and get you thinking more creatively. And even if you fail, your result might be that you achieve only a 25% or a 10% increase. How much additional money would that represent to you over the next five years of sales? What influence, if any, did the title of this article have on your decision to read it? If I had titled this article, “Registration Incentives,” would you have been more or less likely to read it now? Note that the title expresses a specific and clear benefit to you. It tells you exactly what you can expect to gain by reading it. Effective registration incentives work the same way. They offer clear, specific benefits to the user if a purchase is made. In order to improve your registration incentives, the first thing you need to do is to adopt some new beliefs that will change your perspective. I’m going to introduce you to what I call the “lies of success” in the shareware industry. These are statements that are not true at all, but if you accept them as true anyway, you’ll achieve far better results than if you don’t. Rule 1: What you are selling is merely the difference between the shareware and the registered versions, not the registered version itself. Note that this is not a true statement, but if you accept it as true, you’ll immediately begin to see the weaknesses in your registration incentives. If there are few additional benefits for buying the full version vs. using the shareware version, then you aren’t offering the user strong enough incentives to make the full purchase. Rule 2: The sole purpose of the shareware version is to close the sale. This is our second lie of success. Note the emphasis on the word “close.” Your shareware version needs to act as a direct sales vehicle. It must be able to take the user all the way to the point of purchase, i.e. your online order form, ideally with nothing more than a few mouse clicks. Anything that detracts from achieving a quick sale is likely to hurt sales. Rule 3: The customer’s perspective is the only one that matters. Defy this rule at your peril. Customers don’t care that you spent 2000 hours creating your product. Customers don’t care that you deserve the money for your hard work. Customers don’t care that you need to do certain things to prevent piracy. All that matters to them are their own personal wants and needs. Yes, these are lies of success. Some customers will care, but if you design your registration incentives assuming they only care about their own self-interests, your motivation to buy will be much stronger than if you merely appeal to their sense of honesty, loyalty, or honor. Assume your customers are all asking, “What’s in it for me if I choose to buy? What will I get? How will this help me?” I don’t care if you’re selling to Fortune 500 companies. At some point there will be an individual responsible for causing the purchase to happen, and that individual is going to consider how the purchase will affect him/her personally: “Will this purchase get me fired? Will it make me look good in front of my peers? Will this make my job easier or harder?” Many shareware developers get caught in the trap of discriminating between honest and dishonest users, believing that honest users will register and dishonest ones won’t. This line of thinking will ultimately get you nowhere, and it violates the third lie of success. When you make a purchase decision, how often do you use honesty as the deciding factor? Do you ever say, “I will buy this because I’m honest?” Or do you consider other more selfish factors first, such as how it will make you feel to purchase the software? The truth is that every user believes s/he is honest, so no user applies the honesty criterion when making a purchase decision. Thinking of your users in terms of honest ones vs. dishonest ones is a complete waste of time because that’s not how users primarily view themselves. Rule 4: Customers buy on emotion and justify with fact. If you’re honest with yourself, you’ll see that this is how you make most purchase decisions. Remember the last time you bought a computer. Is it fair to say that you first became emotionally attached to the idea of owning a new machine? For me, it’s the feeling of working faster, owning the latest technology, and being more productive that motivates me to go computer shopping. Once I’ve become emotionally committed, the justifications follow: “It’s been two years since I’ve upgraded, it will pay for itself with the productivity boost I gain, I can easily afford it, I’ve worked hard and I deserve a new machine, etc.” You use facts to justify the purchase. Once you understand how purchase decisions are made, you can see that your shareware products need to first get the user emotionally invested in the purchase, and then you give them all the facts they need to justify it. Now that we’ve gotten these four lies of success out of the way, let’s see how we might apply them to create some compelling registration incentives. Let’s start with Rule 1. What incentives can be spawned from this rule? The common 30-day trial is one obvious derivative. If you are only selling the difference between the shareware and registered versions, then a 30-day trial implies that you are selling unlimited future days of usage of the program after the trial period expires. This is a powerful incentive, and it’s been proven effective for products that users will continue to use month after month. 30-day trials are easy for users to understand, and they’re also easy to implement. You could also experiment with other time periods such as 10 days, 14 days, or 90 days. The only way of truly knowing which will work best for your products is to experiment. But let’s see if we can move a bit beyond the basic 30-day trial here by mixing in a little of Rule 3. How would the customer perceive a 30-day trial? In most cases 30 days is plenty of time to evaluate a product. But in what situations would a 30-day trial have a negative effect? A good example is when the user downloads, installs, and briefly checks out a product s/he may not have time to evaluate right away. By the time the user gets around to fully evaluating it, the shareware version has already expired, and a sale may be lost as a result. To get around this limitation, many shareware developers have started offering 30 days of actual program usage instead of 30 consecutive days. This allows the user plenty of time to try out the program at his/her convenience. Another possibility would be to limit the number of times the program can be run. The basic idea is that you are giving away limited usage and selling unlimited usage of the program. This incentive definitely works if your product is one that will be used frequently over a long period of time (much longer than the trial period). The flip side of usage limitation is to offer an additional bonus for buying within a certain period of time. For instance, in my game Dweep, I offer an extra 5 free bonus levels to everyone who buys within the first 10 days. In truth I give the bonus levels to everyone who buys, but the incentive is real from the customer’s point of view. Remember Rule 3 - it doesn’t matter what happens on my end; it only matters what the customer perceives. Any customer that buys after the first 10 days will be delighted anyway to receive a bonus they thought they missed. So if your product has no time-based incentives at all, this is the first place to start. When would you pay your bills if they were never due, and no interest was charged on late payments? Use time pressure to your advantage, either by disabling features in the shareware version after a certain time or by offering additional bonuses for buying sooner rather than later. If nothing else and if it’s legal in your area, offer a free entry in a random monthly drawing for a small prize, such as one of your other products, for anyone who buys within the first X days. Another logical derivative of Rule 1 is the concept of feature limitation. On the crippling side, you can start with the registered version and begin disabling functionality to create the shareware version. Disabling printing in a shareware text editor is a common strategy. So is corrupting your program’s output with a simple watermark. For instance, your shareware editor could print every page with your logo in the background. Years ago the Association of Shareware Professionals had a strict policy against crippling, but that policy was abandoned, and crippling has been recognized as an effective registration incentive. It is certainly possible to apply feature limitation without having it perceived as crippling. This is especially easy for games, which commonly offer a limited number of playable levels in the shareware version with many more levels available only in the registered version. In this situation you offer the user a seemingly complete experience of your product in the shareware version, and you provide additional features on top of that for the registered version. Time-based incentives and feature-based incentives are perhaps the two most common strategies used by shareware developers for enticing users to buy. Which will work best for you? You will probably see the best results if you use both at the same time. Imagine you’re the end user for a moment. Would you be more likely to buy if you were promised additional features and given a deadline to make the decision? I’ve seen several developers who were using only one of these two strategies increase their registration rates dramatically by applying the second strategy on top of the first. If you only use time-based limitations, how could you apply feature limitation as well? Giving the user more reasons to buy will translate to more sales per download. One you have both time-based and feature-based incentives to buy, the next step is to address the user’s perceived risk by applying a risk-reversal strategy. Fortunately, the shareware model already reduces the perceived risk of purchasing significantly, since the user is able to try before buying. But let’s go a little further, keeping Rule 3 in mind. What else might be a perceived risk to the user? What if the user reaches the end of the trial period and still isn’t certain the product will do what s/he needs? What if the additional features in the registered version don’t work as the user expects? What can we do to make the decision to purchase safer for the user? One approach is to offer a money-back guarantee. I’ve been offering a 60-day unconditional money-back guarantee on all my products since January 2000. If someone asks for their money back for any reason, I give them a full refund right away. So what is my return rate? Well, it’s about 8%. Just kidding! Would it surprise you to learn that my return rate at the time of this writing is less than 0.2%? Could you handle two returns out of every 1000 sales? My best estimate is that this one technique increased my sales by 5-10%, and it only took a few minutes to implement. When I suggest this strategy to other shareware developers, the usual reaction is fear. “But everyone would rip me off,” is a common response. I suggest trying it for yourself on an experimental basis; a few brave souls have already tried it and are now offering money-back guarantees prominently. Try putting it up on your web site for a while just to convince yourself it works. You can take it down at any time. After a few months, if you’re happy with the results, add the guarantee to your shareware products as well. I haven’t heard of one bad outcome yet from those who’ve tried it. If you use feature limitation in your shareware products, another important component of risk reversal is to show the user exactly what s/he will get in the full version. In Dweep I give away the first five levels in the demo version, and purchasing the full version gets you 147 more levels. When I thought about this from the customer’s perspective (Rule 3), I realized that a perceived risk is that s/he doesn’t know if the registered version levels will be as fun as the demo levels. So I released a new demo where you can see every level but only play the first five. This lets the customer see all the fun that awaits them. So if you have a feature-limited product, show the customer how the feature will work. For instance, if your shareware version has printing disabled, the customer could be worried that the full version’s print capability won’t work with his/her printer or that the output quality will be poor. A better strategy is to allow printing, but to watermark the output. This way the customer can still test and verify the feature, and it doesn’t take much imagination to realize what the output will look like without the watermark. Our next step is to consider Rule 2 and include the ability close the sale. It is imperative that you include an “instant gratification” button in your shareware products, so the customer can click to launch their default web browser and go directly to your online order form. If you already have a “buy now” button in your products, go a step further. A small group of us have been finding that the more liberally these buttons are used, the better. If you only have one or two of these buttons in your shareware program, you should increase the count by at least an order of magnitude. The current Dweep demo now has over 100 of these buttons scattered throughout the menus and dialogs. This makes it extremely easy for the customer to buy, since s/he never has to hunt around for the ordering link. What should you label these buttons? “Buy now” or “Register now” are popular, so feel free to use one of those. I took a slightly different approach by trying to think like a customer (Rule 3 again). As a customer the word “buy” has a slightly negative association for me. It makes me think of parting with my cash, and it brings up feelings of sacrifice and pressure. The words “buy now” imply that I have to give away something. So instead, I use the words, “Get now.” As a customer I feel much better about getting something than buying something, since “getting” brings up only positive associations. This is the psychology I use, but at present, I don’t know of any hard data showing which is better. Unless you have a strong preference, trust your intuition. Make it as easy as possible for the willing customer to buy. The more methods of payment you accept, the better your sales will be. Allow the customer to click a button to print an order form directly from your program and mail it with a check or money order. On your web order form, include a link to a printable text order form for those who are afraid to use their credit cards online. If you only accept two or three major credit cards, sign up with a registration service to handle orders for those you don’t accept. So far we’ve given the customer some good incentives to buy, minimized perceived risk, and made it easy to make the purchase. But we haven’t yet gotten the customer emotionally invested in making the purchase decision. That’s where Rule 4 comes in. First, we must recognize the difference between benefits and features. We need to sell the sizzle, not the steak. Features describe your product, while benefits describe what the user will get by using your product. For instance, a personal information manager (PIM) program may have features such as daily, weekly, and monthly views; task and event timers; and a contact database. However, the benefits of the program might be that it helps the user be more organized, earn more money, and enjoy more free time. For a game, the main benefit might be fun. For a nature screensaver, it could be relaxation, beauty appreciation, or peace. Features are logical; benefits are emotional. Logical features are an important part of the sale, but only after we’ve engaged the customer’s emotions. Many products do a fair job of getting the customer emotionally invested during the trial period. If you have an addictive program or one that’s fun to use, such as a game, you may have an easy time getting the customer emotionally attached to using it because the experience is already emotional in nature. But whatever your product is, you can increase your sales by clearly illustrating the benefits of making the purchase. A good place to do this is in your nag screens. I use nag screens both before and after the program runs to remind the user of the benefits of buying the full version. At the very least, include a nag screen when the customer exits the program, so the last thing s/he sees will be a reminder of the product’s benefits. Take this opportunity to sell the user on the product. Don’t expect features like “customizable colors” to motivate anyone to buy. Paint a picture of what benefits the user will obtain with the full version. Will I save time? Will I have more fun? Will I live longer, save money, or feel better? The simple change from feature-oriented selling to benefit-oriented selling can easily double or triple your sales. Be sure to use this approach on your web site as well if you don’t already. Developers who’ve recently made the switch have been reporting some amazing results. If you’re drawing a blank when trying to come up with benefits for your products, the best thing you can do is to email some of your old customers and ask them why they bought your program. What did it do for them? I’ve done this and was amazed at the answers I got back. People were buying my games for reasons I’d never anticipated, and that told me which benefits I needed to emphasize in my sales pitch. The next key is to make your offer irresistible to potential customers. Find ways to offer the customer so much value that it would be harder to say no than to say yes. Take a look at your shareware product as if you were a potential customer who’d never seen it before. Being totally honest with yourself, would you buy this program if someone else had written it? If not, don’t stop here. As a potential customer, what additional benefits or features would put you over the top and convince you to buy? More is always better than less. In the original version of Dweep, I offered ten levels in the demo and thirty in the registered version. Now I offer only five demo levels and 152 in the full version, plus a built-in level editor. Originally, I offered the player twice the value of the demo; now I’m offering over thirty times the value. I also offer free hints and solutions to every level; the benefit here is that it minimizes player frustration. As I keep adding bonuses for purchasing, the offer becomes harder and harder to resist. What clever bonuses can you throw in for registering? Take the time to watch an infomercial. Notice that there is always at least one “FREE” bonus thrown in. Consider offering a few extra filters for an image editor, ten extra images for a screensaver, or extra levels for a game. What else might appeal to your customers? Be creative. Your bonus doesn’t even have to be software-based. Offer a free report about building site traffic with your HTML editor, include an essay on effective time management with your scheduling program, or throw in a small business success guide with your billing program. If you make such programs, you shouldn’t have too much trouble coming up with a few pages of text that would benefit your customers. Keep working at it until your offer even looks irresistible to you. If all the bonuses you offer can be delivered electronically, how many can you afford to include? If each one only gains one more customer in a thousand (0.1%), would it be worth the effort over the lifetime of your sales? So how do you know if your registration incentives are strong enough? And how do you know if your product is over-crippled? Where do you draw the line? These are tough issues, but there is a good way to handle them if your product is likely to be used over a long period of time, particularly if it’s used on a daily basis. Simply make your program gradually increase its registration incentives over time. One easy way to do this is with a delay timer on your nag screens that increases each time the program is run. Another approach is to disable certain features at set intervals. You begin by disabling non-critical features and gradually move up to disabling key functionality. The program becomes harder and harder to continue using for free, so the benefits of registering become more and more compelling. Instead of having your program completely disable itself after your trial period, you gradually degrade its usability with additional usage. This approach can be superior to a strict 30-day trial, since it allows your program to still be used for a while, but after prolonged usage it becomes effectively unusable. However, you don’t simply shock the user by taking away all the benefits s/he has become accustomed to on a particular day. Instead, you begin with a gentle reminder that becomes harder and harder to ignore. There may be times when your 30-day trial shuts off at an inconvenient time for the user, and you may lose a sale as a result. For instance, the user may not have the money at the time, or s/he may be busy at the trial’s end and forget to register. In that case s/he may quickly replace what was lost with a competitor’s trial version. The gradual degradation approach allows the user to continue using your product, but with increasing difficulty over time. Eventually, there is a breaking point where the user either decides to buy or to stop using the program completely, but this can be done within a window of time at the user’s convenience. Hopefully this article has gotten you thinking creatively about all the overlooked ways you can entice people to buy your shareware products. The most important thing you can do is to begin seeing your products through your customers’ eyes. What additional motivation would convince you to buy? What would represent an irresistible offer to you? There is no limit to how many incentives you can add. Don’t stop at just one or two; instead, give the customer a half dozen or more reasons to buy, and you’ll see your registration rate soar. Is it worth spending a day to do this? I think so.

yesterday 4 votes
Maybe writing speed actually is a bottleneck for programming

I'm a big (neo)vim buff. My config is over 1500 lines and I regularly write new scripts. I recently ported my neovim config to a new laptop. Before then, I was using VSCode to write, and when I switched back I immediately saw a big gain in productivity. People often pooh-pooh vim (and other assistive writing technologies) by saying that writing code isn't the bottleneck in software development. Reading, understanding, and thinking through code is! Now I don't know how true this actually is in practice, because empirical studies of time spent coding are all over the place. Most of them, like this study, track time spent in the editor but don't distinguish between time spent reading code and time spent writing code. The only one I found that separates them was this study. It finds that developers spend only 5% of their time editing. It also finds they spend 14% of their time moving or resizing editor windows, so I don't know how clean their data is. But I have a bigger problem with "writing is not the bottleneck": when I think of a bottleneck, I imagine that no amount of improvement will lead to productivity gains. Like if a program is bottlenecked on the network, it isn't going to get noticeably faster with 100x more ram or compute. But being able to type code 100x faster, even with without corresponding improvements to reading and imagining code, would be huge. We'll assume the average developer writes at 80 words per minute, at five characters a word, for 400 characters a minute.What could we do if we instead wrote at 8,000 words/40k characters a minute? Writing fast Boilerplate is trivial Why do people like type inference? Because writing all of the types manually is annoying. Why don't people like boilerplate? Because it's annoying to write every damn time. Programmers like features that help them write less! That's not a problem if you can write all of the boilerplate in 0.1 seconds. You still have the problem of reading boilerplate heavy code, but you can use the remaining 0.9 seconds to churn out an extension that parses the file and presents the boilerplate in a more legible fashion. We can write more tooling This is something I've noticed with LLMs: when I can churn out crappy code as a free action, I use that to write lots of tools that assist me in writing good code. Even if I'm bottlenecked on a large program, I can still quickly write a script that helps me with something. Most of these aren't things I would have written because they'd take too long to write! Again, not the best comparison, because LLMs also shortcut learning the relevant APIs, so also optimize the "understanding code" part. Then again, if I could type real fast I could more quickly whip up experiments on new apis to learn them faster. We can do practices that slow us down in the short-term Something like test-driven development significantly slows down how fast you write production code, because you have to spend a lot more time writing test code. Pair programming trades speed of writing code for speed of understanding code. A two-order-of-magnitude writing speedup makes both of them effectively free. Or, if you're not an eXtreme Programming fan, you can more easily follow the The Power of Ten Rules and blanket your code with contracts and assertions. We could do more speculative editing This is probably the biggest difference in how we'd work if we could write 100x faster: it'd be much easier to try changes to the code to see if they're good ideas in the first place. How often have I tried optimizing something, only to find out it didn't make a difference? How often have I done a refactoring only to end up with lower-quality code overall? Too often. Over time it makes me prefer to try things that I know will work, and only "speculatively edit" when I think it be a fast change. If I could code 100x faster it would absolutely lead to me trying more speculative edits. This is especially big because I believe that lots of speculative edits are high-risk, high-reward: given 50 things we could do to the code, 49 won't make a difference and one will be a major improvement. If I only have time to try five things, I have a 10% chance of hitting the jackpot. If I can try 500 things I will get that reward every single time. Processes are built off constraints There are just a few ideas I came up with; there are probably others. Most of them, I suspect, will share the same property in common: they change the process of writing code to leverage the speedup. I can totally believe that a large speedup would not remove a bottleneck in the processes we currently use to write code. But that's because those processes are developed work within our existing constraints. Remove a constraint and new processes become possible. The way I see it, if our current process produces 1 Utils of Software / day, a 100x writing speedup might lead to only 1.5 UoS/day. But there are other processes that produce only 0.5 UoS/d because they are bottlenecked on writing speed. A 100x speedup would lead to 10 UoS/day. The problem with all of this that 100x speedup isn't realistic, and it's not obvious whether a 2x improvement would lead to better processes. Then again, one of the first custom vim function scripts I wrote was an aid to writing unit tests in a particular codebase, and it lead to me writing a lot more tests. So maybe even a 2x speedup is going to be speed things up, too. Patreon Stuff I wrote a couple of TLA+ specs to show how to model fork-join algorithms. I'm planning on eventually writing them up for my blog/learntla but it'll be a while, so if you want to see them in the meantime I put them up on Patreon.

2 days ago 6 votes
Occupation and Preoccupation

Here’s Jony Ive in his Stripe interview: What we make stands testament to who we are. What we make describes our values. It describes our preoccupations. It describes beautiful succinctly our preoccupation. I’d never really noticed the connection between these two words: occupation and preoccupation. What comes before occupation? Pre-occupation. What comes before what you do for a living? What you think about. What you’re preoccupied with. What you think about will drive you towards what you work on. So when you’re asking yourself, “What comes next? What should I work on?” Another way of asking that question is, “What occupies my thinking right now?” And if what you’re occupied with doesn’t align with what you’re preoccupied with, perhaps it's time for a change. Email · Mastodon · Bluesky

2 days ago 3 votes
American hype

There's no country on earth that does hype better than America. It's one of the most appealing aspects about being here. People are genuinely excited about the future and never stop searching for better ways to work, live, entertain, and profit. There's a unique critical mass in the US accelerating and celebrating tomorrow. The contrast to Europe couldn't be greater. Most Europeans are allergic to anything that even smells like a commercial promise of a better tomorrow. "Hype" is universally used as a term to ridicule anyone who dares to be excited about something new, something different. Only a fool would believe that real progress is possible! This is cultural bedrock. The fault lines have been settling for generations. It'll take an earthquake to move them. You see this in AI, you saw it in the Internet. Europeans are just as smart, just as inventive as their American brethren, but they don't do hype, so they're rarely the ones able to sell the sizzle that public opinion requires to shift its vision for tomorrow.  To say I have a complicated relationship with venture capital is putting it mildly. I've spent a career proving the counter narrative. Proving that you can build and bootstrap an incredible business without investor money, still leave a dent in the universe, while enjoying the spoils of capitalism. And yet... I must admit that the excesses of venture capital are integral to this uniquely American advantage on hype. The lavish overspending during the dot-com boom led directly to a spectacular bust, but it also built the foundation of the internet we all enjoy today. Pets.com and Webvan flamed out such that Amazon and Shopify could transform ecommerce out of the ashes. We're in the thick of peak hype on AI right now. Fantastical sums are chasing AGI along with every dumb derivative mirage along the way. The most outrageous claims are being put forth on the daily. It's easy to look at that spectacle with European eyes and roll them. Some of it is pretty cringe! But I think that would be a mistake. You don't have to throw away your critical reasoning to accept that in the face of unknown potential, optimism beats pessimism. We all have to believe in something, and you're much better off believing that things can get better than not.  Americans fundamentally believe this. They believe the hype, so they make it come to fruition. Not every time, not all of them, but more of them, more of the time than any other country in the world. That really is exceptional.

2 days ago 3 votes
File sync is very slow

I’m working on a Go library appendstore for append-only store of lots of things in a single file. To make things as robust as possible I was calling os.File.Sync() after each append. Sync() is waiting until the data is acknowledged as truly, really written to disk (as opposed to maybe floating somewhere in disk drive’s write buffer). Oh boy, is it slow. A test of appending 1000 records would take over 5 seconds. After removing the Sync() it would drop to 5 milliseconds. 1000x faster. I made sync optional - it’s now up to the user of the library to pick it, defaults to non-sync. Is it unsafe now? Well, the reality is that it probably doesn’t matter. I don’t think lots of software does the sync due to slowness and the world still runs.

2 days ago 2 votes