In case you don’t know Herbert Lui’s work, he’s a writer, editorial director, and book author. He wrote Creative Doing, a book with exercises and prompts for writers and creatives.
After binge-reading his blog, I learned these lessons from him.
1. Don’t Start From Scratch
Be a DJ producer of ideas instead.
Instead of trying to come up with original ideas every time, mix and build on your past and maybe forgotten ideas.
If you want to improve at something, go for quantity.
That’s the key finding of the story of the pottery class: A teacher divided his pottery class into two halves. The first half was graded on the quantity of pottery produced and the other half on the quality instead. At the end of the class, the half graded on quantity did best. Their effort was to produce something and quickly move on to the next piece, taking the feedback from past repetitions.
If you need ideas to go for quantity, do a 100-day challenge: finish something every day for 100 days. Herbert wrote for 100 days and shared his lessons.
Quality comes after quantity.
3. You Don’t Run Out of Ideas if You Know Where To Look
When you think you’re running out of ideas, go through your favorite blogs, YouTube channels, and your social feeds and write a reaction post for a piece you liked.
A post doesn’t have to be a 2,000-word masterpiece.
To remove the pressure of writing masterpieces every time, consider a blog like a garden of ideas instead of a finished and polished product. You can always expand a past idea on a new piece.
Most of Herbert’s posts only have a headline and one main idea. You’re free to break the rule of writing introductions and conclusions. That’s something I also noticed by reading Seth Godin’s blog. They write the main idea naturally after the headline without an introduction.
Finding Herbert’s blog inspired me to continue writing. Herbert doesn’t seem to have a “niche.” Maybe everything he writes falls under the umbrella of writing and creativity.
Don’t have a niche and go for quantity. You don’t need to write masterpieces, only to document you journey.
Running out of ideas is the greatest fear of new writers.
But don’t worry. You’re not running out of ideas. You have plenty of them. You need to be aware of the content you consume and have writing prompts to help you write.
Here are 3 tips to never run out of ideas to write:
1. Become a DJ of Ideas
There’s nothing new under the sun. And it has all been done before.
That’s relieving and reassuring. That removes the self-imposed pressure of coming up with new and original ideas.
Never start from scratch again. Develop your “new” ideas by remixing and expanding your past ideas or others’ ideas. Become a DJ producer of ideas.
If you think you’re running out of ideas, go through your posts, videos, or social feed and write a reaction post about one piece of content.
2. Follow the 20-Min Rule
If something takes you 20 minutes or more to figure out, write about it.
This is the prompt I’ve used to write some of my coding tutorials. Once I figure out how to do something after being stuck for a while, I write about it.
I wish I could credit the source of this rule. I remember learning about it from an old YouTube presentation about technical blogging that I can’t find anymore.
So if you have an aha moment, write about it. If you finally understood a hard subject, write about it.
By writing about your learning struggles, you will reinforce and document your learning.
3. Follow the 3-Strike Rule
If a subject or idea comes up at least three times around you, write about it.
I found this idea on Shawn Wang (swyx)’s blog in the context of technical blogging. But it can be easily extended outside the coding world too.
If another client asks you the same question, write about it. And if you give the same piece of advice to someone, guess what? Write about it.
With the 3-strike rule, you’re also saving your keystrokes. After 3 strikes, the next time the same subject comes up, just share a link to your writing.
Now, you’re not running out of ideas again. It’s impossible. Notice the content you’re consuming and write about it.
My first coding job was far from being like a Silicon Valley job at a startup.
I didn’t have ping-pong tables or slides to go between offices.
It was, by all means, a boring job at a local non-tech company. There’s nothing wrong with a boring job if that’s what you want. But it taught me valuable lessons about life, coding, and money. Here they are.
You Are Not Your Code
My first job was with a small team. One team member took care of one project, from start to end, wearing multiple hats.
Things got complicated when we had to work together and inherited the code from a coworker. It was messy code, to say the least. He put all actions and logic inside event handlers. Those were the days of WinForms applications: Drag and drop a button, double-click, and connect to the database right there.
We had to fix his issues and rewrite his code. Nobody wanted to do that. And I started to judge him because of his code. “What if he does everything else the same way he codes?”
Don’t judge someone by their code. Don’t take it personally either. You could miss professional connections or friendships by judging people for their code.
Assume everyone does their best with the resources they have at hand.
In the future, someone will inherit your code and say “What a crappy code. Who wrote this?”
There will always be different opinions and better ways of doing things. And even you will think of better ways to do your current work!
Coding Is Not the Only Thing
In my first days, I only wanted to code.
I had just finished reading the Clean Code and wanted to refactor everything around me. I didn’t want to attend meetings, answer phone calls, or even reply to emails. I only wanted to code. That’s what I was paid for, right?
More than once, my boss called me to his office and I arrived minutes late because I was coding in my cubicle. I don’t know why I didn’t get in trouble for that.
Software engineering is about collaboration.
You won’t be locked in a basement coding. You must talk to clients, conduct meetings, agree on estimations, and ask for help.
I had to learn there’s more than just typing symbols in text files.
Live With Half of Your Salary
That’s the best life advice I’ve ever received for free.
My cubicle was next to the coffee machine, in a corner that had once been a bathroom.
One day, another coworker, a “veteran of many battles,” came over to have his coffee. And he said something like this:
“Hey Cesar, here’s a free piece of advice. Now that you can, imagine you only make half your salary and save the other half. Sooner than later, you can buy your own apartment.”
Years later, while reading money books, I found similar advice. And it reminded me of that conversation.
I’ve followed that advice, but not exactly. I’ve saved less than half of my salary.
Today, I’d rephrase it like this: “Imagine you make half of your salary, save, and invest the other half.”
You Don’t Have To Feel Miserable
I thought talent and good work were shortcuts to breaking the rules of the corporate world. I was soooo wrong! The Matrix is real!
Endless meetings, office politics, and a fixed schedule.
It all started to take its toll on me.
There were days when I felt I was leaving my life behind while sitting at a computer. I felt demotivated and disengaged. I was craving variety and change. I didn’t know there was a term for that: burnout.
Always have an exit plan.
Change jobs when you wake up and can’t get out of bed to work.
Find a way to motivate yourself: start a side project, learn a new tech stack, or discover a new way of doing your work. Or simply update your CV and LinkedIn profile and move on.
Parting Thoughts
It’s been more than 10 years since my first job. I’m only grateful for it. Somebody took a leap of faith with me and gave me a chance when I had 0 hours of flight time.
I took these lessons to my next job. And every time I can, I give the same money advice my coworker gave me: save and invest half of your salary.
Often what we value the most from past jobs is not the money, but the friendships and connections. From time to time, I meet with coworkers I met at this first job for coffee.
C# is getting more and more functional with every release.
I don’t mean functional in the sense of being practical or not. I mean C# is borrowing features from functional languages, like records from F#, while staying a multi-paradigm language.
Yes, C# will never be a fully functional language. And that’s by design.
But, it still misses one key feature from functional languages: Discriminated Unions.
Discriminated Unions Are a Closed Hierarchy of Classes
Think of discriminated unions like enums where each member could be an object of a different, but somehow related, type.
Let me show you an example where a discriminated union makes sense. At a past job, while working with a reservation management software, hotels wanted to charge a deposit before the guests arrived. They wanted to charge some nights, a fixed amount, or a percentage of room charges, right after getting the reservation or before the arrival date.
Here’s how to represent that requirement with a discriminated union:
publicrecordDeposit(ChargeCharge,DelayDelay);// Of course, I'm making this up...// This is invalid syntaxpublicunionrecordCharge// <--{NightCountNights;FixedAmountAmount;PercentagePercentage;}publicrecordNightCount(intCount);publicrecordFixedAmount(decimalAmount,CurrencyCodeCurrency);publicenumCurrencyCode{USD,Euro,MXN,COP}publicrecordPercentage(decimalAmount);publicrecordDelay(intDays,DelayTypeDelayType);publicenumDelayType{BeforeCheckin,AfterReservation}
Here, the Charge class would be a discriminated union. It could only hold one of three values: NightCount, FixedAmount, or Percentage.
You might be thinking it looks like a regular hierarchy of classes. And that’s right.
But, the missing piece is that discriminated unions are exhaustive. We don’t need a default case when using a discriminated union inside a switch. And, if we add a new member to the discriminated union, the compiler will warn us about where we should handle the new member.
Discriminated unions are helpful to express restrictions, constraints, and business rules when working with Domain Driven Design. In fact, using types to represent business rules is the main idea of the book Domain Modeling Made Functional.
For example, discriminated unions are a good alternative when moving I/O to the edges of our apps and just returning decisions from our domains.
I told you that C# is borrowing some new features from functional languages. Well, if you’re curious, this is how our example will look like in F# using real discriminated unions:
typeDeposit={Charge:Charge;Delay:Delay;}typeCharge=// <-- Look, ma! A discriminated union|NightCountofint|FixedAmountofdecimal*CurrencyCode|PercentageofdecimaltypeCurrencyCode=|USD|Euro|MXN|COPtypeDelay=|BeforeCheckinofint|AfterReservationofint
All credits to Phind, “an intelligent answer engine for developers,” for writing that F# example.
A Proposal for a Union Keyword
It seems the C# language team is considering fully supporting discriminated unions.
In the official C# GitHub repository, there’s a recent proposal for discriminated unions. The goal is to create a new type to “store one of a limited number of other types in the same place” and let C# do all the heavy work to handle variables of that new type, including checking for exhaustiveness.
The proposal suggests introducing a new union type for classes and structs. Our example using the union type will look like this:
publicunionCharge// ^^^^^{NightCount(intCount);FixedAmount(decimalAmount,CurrencyCodeCurrency);Percentage(decimalAmount);}// This is how to instantiate a union typeChargechargeOneNight=newNightCount(1);// <--varoneNightBeforeCheckin=newDeposit(chargeOneNight// ^^^^^newDelay(1,DelayType.BeforeCheckin));// This is how to use it inside a switchvaramountToCharge=chargeswitch{NightCountn=>DoSomethingHere(n),FixedAmounta=>DoSomethingElseHere(a),Percentagep=>DoSomethingDifferentHere(p)// No need to declare a default case here...}
The new union type will support pattern matching and deconstruction too.
Under the hood, the union type will get translated to a hierarchy of classes, with the base class annotated with a new [Closed] attribute. And, if the default union type doesn’t meet our needs, we can use that new attribute directly.
Two Alternatives While We Wait
The union type is still under discussion. We’ll have to wait.
In the meantime, we can emulate this behavior using third-party libraries like OneOf.
Here’s how to define our Charge type using OneOf:
publicclassCharge:OneOfBase<NightCount,FixedAmount,Percentage>// ^^^^^{publicCharge(OneOf<NightCount,FixedAmount,Percentage>input):base(input){}}// Or using OneOf Source Generation:////[GenerateOneOf] // <--//public partial class Charge : OneOfBase<NightCount, FixedAmount, Percentage>// ^^^^^//{//}publicrecordNightCount(intCount);// <-- No base class herepublicrecordFixedAmount(decimalAmount,CurrencyCodeCurrency);publicrecordPercentage(decimalAmount);// Here's how to instantiate a OneOf typevaroneNightBeforeCheckin=newDeposit(newCharge(newNightCount(1)),// ^^^^^newDelay(1,DelayType.BeforeCheckin));
OneOf brings methods like Match, Value, and AsT0/AsT1/AsT2 to work with and unwrap the underlying type.
Apart from third-party libraries to emulate discriminated unions, there’s an alternative approach abusing records: Discriminated Onions.
And here’s our Charge type using discriminated onions:
publicabstractrecordCharge// ^^^^^{privateCharge(){}// <--publicrecordNightCount(intCount):Charge;// <--publicrecordFixedAmount(decimalAmount,CurrencyCodeCurrency):Charge;publicrecordPercentage(decimalAmount):Charge;publicUMatch<U>(// ^^^^^Func<NightCount,U>onNightCount,Func<FixedAmount,U>onFixedAmount,Func<Percentage,U>onPercentage)=>thisswitch{NightCountr=>onNightCount(r),FixedAmountc=>onFixedAmount(c),Percentagep=>onPercentage(p),_=>thrownewArgumentOutOfRangeException()};}// Here's how to instantiate a discriminated onionvaroneNightBeforeCheckin=newDeposit(newCharge.NightCount(1),newDelay(1,DelayType.BeforeCheckin));
Voilà! That’s what discriminated unions are, and the official proposal to bring them to the C# language.
You see, C# is getting more functional on each release. While we wait to get it more funcy with discriminated unions, we have to go with libraries and workarounds. Let’s wait to see how it goes.
That’s how I’d summarize the talk “Moving IO to the edges of your app” by Scott Wlaschin at NDC Sydney 2024.
In case you don’t know Scott Wlaschin’s work, he runs the site F# for Fun and Profit and talks about Functional Programming a lot. He’s a frequent speaker at the NDC Conference.
Here’s the YouTube video of the talk, in case you want to watch it:
These are the main takeaways from that talk and how I’d follow them to refactor a piece of code from one of my past projects.
I/O Is Evil: Keep It at Arm’s Length
In a perfect world, all code should be pure. The same inputs return the same outputs with no side effects.
But we’re not in a perfect world, and our code is full of impurities: retrieving the current time, accessing the network, and calling databases.
Instead of aiming for 100% pure code, the guideline is to move I/O (or impurities) away from the business logic or rules.
When we mix I/O with our domain logic, we make our domain logic harder to understand and test, and more error-prone.
So let’s pay attention to functions with no inputs or no outputs. Often, they do I/O somewhere.
If you think we don’t write functions with no outputs, let’s take another look at our repositories.
Sure, our Create or Update methods might return an ID. But they’re not deterministic. If we insert the same record twice, we get different IDs or even an error if we have unique constraints in our tables.
The guideline here is to write code that is:
Comprehensible: it receives what it needs as input and returns some output.
Deterministic: it returns the same outputs, given the same input.
Free of side effects: it doesn’t do anything under the hood.
Just Return the Decision
This is the example shown in the talk:
Let’s say we need to update a customer’s personal information. If the customer changes their email, we should send a verification email. And, of course, we should update the new name and email in the database.
We’re mixing the database calls with our decision-making code. IO is “close” to our business logic.
Of course, we might argue static methods are a bad idea and pass two interfaces instead: ICustomerDb and IEmailServer. But we’re still mixing IO with business logic.
This time, the guideline is to create an imperative shell and just return the decision from our business logic.
Here’s how to update our customers “just returning the decision,”
enumUpdateCustomerDecision{DoNothing,UpdateCustomerOnly,UpdateCustomerAndSendEmail}// This is a good place for discriminated unions.// But we still don't have them in C#. Sorry!recordUpdateCustomerResult(UpdateCustomerDecisionDecision,Customer?Customer,EmailMessage?Message);staticUpdateCustomerResultUpdateCustomer(Customerexisting,CustomernewCustomer){varresult=newUpdateCustomerResult(UpdateCustomerDecision.DoNothing,null,null);if(existing.Name!=newCustomer.Name||existing.EmailAddress!=newCustomer.EmailAddress){result=resultwith{Decision=UpdateCustomerDecision.UpdateCustomerOnly,Customer=newCustomer};}if(existing.EmailAddress!=newCustomer.EmailAddress){varmessage=newEmailMessage(newCustomer.EmailAddress,"Some message here...");result=resultwith{Decision=UpdateCustomerDecision.UpdateCustomerAndSendEmail,Message=message};}returnresult;}asyncstaticTaskImperativeShell(CustomernewCustomer){varexisting=awaitCustomerDb.ReadCustomer(newCustomer.Id);varresult=UpdateCustomer(existing,newCustomer);// ^^^^^// Nothing impure hereswitch(result.Decision){caseDoNothing:// Well, doing nothing...break;caseUpdateCustomerOnly:// Updating the database here...break;caseUpdateCustomerAndSendEmail:// Update the database here...// And, send the email here...break;}}
With the imperative shell, we don’t have to deal with database calls and email logic inside our UpdateCustomer(). And we can unit test it without mocks.
As a side note, UpdateCustomerDecision and UpdateCustomerResult are a simple alternative to discriminated unions. Think of discriminated unions like enums where each member could be an object of a different type.
In more complex codebases, ImperativeShell() would be like a use case class or command handler.
Pure Code Doesn’t Talk to the Outside
When we push I/O to the edges, our pure code doesn’t need exception handling or asynchronous logic. Our pure code doesn’t talk to the outside world.
These are the three code smells the speaker shared to watch out for in our domain code:
Is it async? If so, you’re doing I/O somewhere
Is it catching exceptions? Again, you’re (probably) doing I/O somewhere
Is it throwing exceptions? Why not use a proper return value?
If any of these are true, we’re doing IO inside our domain. And we should refactor our code. “All hands man your refactoring stations.”
Moving I/O to the Edges When Sending an Email
While watching this talk, I realized I could refactor some code I wrote for sending emails in a past project.
Before sending an email, we need to validate if we’re sending it to valid domains. And, after calling a third-party email service, we should store a tracking number and update the email status. Something like this,
publicclassEmail{// Imagine more properties like From, Subject, Body here...privatereadonlyIEnumerable<Recipient>_recipients=newList<Recipient>();publicasyncTaskSendAsync(IEmailServiceemailService,IDomainValidationServicevalidationService,CancellationTokencancellationToken){try{awaitvalidationService.ValidateAsync(this,cancellationToken);// It assumes that ValidateAsync changes the recipient's statusif(_recipients.Any(t=>t.LastStatus!=DeliveryStatus.FailedOnSend)){vartrackingId=awaitemailService.SendEmailAsync(this,cancellationToken);SetTrackingId(trackingId);MarkAsSentToProvider();}}catch(Exceptionex){UpdateStatus(DeliveryStatus.FailedOnSend);thrownewSendEmailException("Sending email failed.",ex);}}}
But this code contains the three code smells we should avoid: it has asynchronous logic and throws and catches exceptions, and even our Domain is aware of cancellation tokens. Arrggg!
That was an attempt to do Domain Driven Design (DDD) at a past team. And probably, our team at that time picked those conventions from the book Hands-on Domain-Driven Design with .NET Core.
And the imperative shell that calls SendAsync() is something like this,
publicclassSendEmailHandler:IEventHandler<EmailCreatedEvent>{// Imagine some fields and a constructor here...publicasyncTaskHandle(EmailCreatedEventevt,CancellationTokencancellationToken){varemail=await_emailRepository.GetByIdAsync(evt.EmailId);??thrownewEmailNotFoundException(evt.EmailId);try{awaitemail.SendAsync(_emailService,_validationService,cancellationToken);await_emailRepository.UpdateAsync(email,cancellationToken);}catch(Exceptionex){email.SetFailedOnSend(ex.Message);await_emailRepository.UpdateAsync(email,cancellationToken);}}}
And here’s the same logic “returning the decision,”
// This is a poor man's discriminated unionpublicabstractrecordSendingAttempt{privateSendingAttempt(){}publicrecordSentToSome(GuidTrackingId,IEnumerable<Recipient>Recipients):SendingAttempt;publicrecordSentToNone():SendingAttempt;publicrecordFailedToSend(stringMessage):SendingAttempt;}publicclassEmail{// Imagine more properties like From, Subject, Body here...privatereadonlyIEnumerable<Recipient>_recipients=newList<Recipient>();publicEmailSend(SendingAttemptattempt){switch(attempt){caseSendingAttempt.SentToSome:// Set trackingId and mark as Sent for some recipients// Mark all other recipients as Invalidbreak;caseSendingAttempt.SentToNone:// Mark all recipients as Invalidbreak;caseSendingAttempt.FailedToSend:// Mark all recipients as Failedbreak;}}}
In this refactored version, we’ve removed the asynchronous logic and exception handling. Now, it receives a SendingAttempt with the result of validating domains and email delivery to the email provider.
Also, it doesn’t have any dependencies passed as interfaces. It embraces Dependency Rejection.
And here’s the imperative shell,
publicclassSendEmailHandler:IEventHandler<EmailCreatedEvent>{// Imagine some fields and a constructor here...publicasyncTaskHandle(EmailCreatedEventevt,CancellationTokencancellationToken){varemail=await_emailRepository.GetByIdAsync(evt.EmailId)??thrownewEmailNotFoundException(evt.EmailId);varresult=await_validationService.ValidateAsync(email,cancellationToken);// Use result to find valid and invalid destinations...// Attempt to send email and catch any exceptions...varsendingAttempt=BuildASendingAttemptHere();email.Send(sendingAttempt);// ^^^^// Nothing impure hereawait_emailRepository.UpdateAsync(email,cancellationToken);}}
Now, the imperative shell validates email domains and tries to send the email, encapsulating all the I/O around Send(). After this refactoring, we should rename Send() inside our domain to something else.
Voila! That’s one approach to have pure business logic, not the one and only approach.
Whether we follow Ports and Adapters, Clean Architecture, or Functional Core-Imperative Shell, the goal is to abstract dependencies and avoid “contaminating” our business domain.