I Hope You Don't Have To Write a CV. But if You Do, Follow These Tips

CVs are so last century.

I hope I don’t have to write a CV anymore. I hope you don’t either.

A personal brand is the new CV and portfolio.

But since you’re reading this one, probably you still need to write a CV. I wish it won’t end up in the dumpsters behind a company careers page. I’ve tried that route with zero results.

More than once, some friend or ex-coworker has asked me to take a look at his CV. I’ve asked them too.

I realized I had already given (and received) the same tips about CVs. These are five tips I always give to write better CVs as software engineers.

1. Keep Your CV to Only One Page

These days of endless scrolling on TikTok, nobody has time and attention to read more than that.

Make it easy for people to review your CV. More than two pages are too long.

Imagine your CV is a business card in a A4 page. You only have one or two minutes to introduce yourself in a business card. Do the same in a CV.

Keep font size, spaces, and borders consistent.

2. Don’t Include a Photo, Marital Status, National ID, or Other Personal Information

Only add your professional email and LinkedIn profile, or any other social media profile to showcase your expertise. GitHub, Medium, dev.to handles work too.

I even skip phone numbers. You don’t want strangers calling or texting you in the middle of the night for a “life changing opportunity at a well-know company in the tech sector.”

You will be asked for personal information once you’re hired, not before.

And only include your city and country if you’re applying for a position that requires being in a particular location. Don’t put your home address either.

3. Don’t Add Every Single Experience Since You Started Working

Use your most recent experience. The last five years, for example.

And, include only the experience relevant to the job you’re applying for. Don’t add the unrelated part-time job you had in your first year at university.

CV, Resume and computer
You see? Even the one in the picture has only one page. Photo by João Ferrão on Unsplash

4. Don’t Include a Cloud of Keywords, Languages, or Library Names

Stars or years working with a language don’t mean expertise.

Probably, you’re riding a bike since you’re 4 or 5, but it doesn’t give you a place in the Tour de France. It’s the same with years of experience with a language or tool. What does five years with XML mean?

Instead, describe what you did and what accomplishments you made in your previous jobs. Use numbers in your job descriptions.

For example, “I wrote an order processing component that scaled up to X orders per month by…“ instead of “C#/ASP.NET Core/SQL Server.” Somebody reading your CV would wonder what you did with those tools.

A bunch of names by themselves mean nothing.

5. Skip Fancy Lines Like “Honest, Hard-Working, Team Player”

Everybody uses the same buzzwords.

“Team player,” “detail-oriented,” and “enthusiast” only take extra space in your single-page CV. Do you still remember tip 1, right? And guess what? Everybody desperate for a job is a detail-oriented enthusiast team player who like to work under pressure in their CV.

Instead of fancy lines, describe in a paragraph what you do and how you can help your next employer.

Voilà! Five tips as promised. Remember recruiters, technical managers, and CTOs will read your CV looking for different information, not only a cloud of languages and libraries.

A CV is to start an interview process. Keep it short and to the point. Be clear. “If you confuse, you lose.”

If you’re interested in more interview preparation material, check these interview types and tips, three interviewing lessons after applying at a FAANG, and follow these ten tips to solve your next take-home coding exercises.

Refactor your coding career with my free 7-day email course. In just 7 emails, you’ll get 10+ years of career lessons to avoid costly career mistakes.

Happy coding!

TIL: How to Use the Specification Pattern in C# to Simplify Repositories

Repositories are the least SOLID part of our codebases.

When we work with Domain-Driven Design, we take care of our business domain and forget about our data-access layer. We end up dumping, in a single interface, every combination of methods and parameters to retrieve our entities from the database. This way, we break the Single Responsibility Principle and Interface Segregation Principle. You see? They’re the least SOLID part.

Our repositories become so bloated that to use one specific method from a repository, we end up depending on a huge interface with lots of other single-use methods. GetOrdersById, GetOrdersByDate, GetLineItemsByOrderId

The Specification Pattern Simplifies Repositories

With the Specification pattern, we extract the “query logic” to another object and away from our repositories.

Instead of making our repositories more specific by adding more methods (_repo.GetOrderById(123456)), the Specification pattern makes repositories more general (_repo.FirstOrDefault(new OrderById(123456))).

Think of a Specification as the query logic and the query parameters to retrieve objects.

Specifications make more sense when using Domain-Driven Design. With Specifications, we encapsulate the LINQ queries, scattered all over our code, inside well-named objects that we keep inside our Domain layer.

Here’s how to use the Ardalis.Specification NuGet package to create a specification and retrieve a list of movies by their release year:

using Ardalis.Specification; // <--
using Ardalis.Specification.EntityFrameworkCore; // <--
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped(typeof(Repository<>)); // <--
builder.Services.AddDbContext<MoviesContext>(options =>
    options.UseInMemoryDatabase("MovieDb"));

var app = builder.Build();

app.MapGet("/movies/{releaseYear}", async (int releaseYear, Repository<Movie> repo) =>
{
    var byReleaseYear = new MoviesByReleaseYear(releaseYear); // <--
    var movies = await repo.ListAsync(byReleaseYear); // <--
        
    // As an alternative, with Ardalis.Specification we can
    // use a specification directly with a DbContext:
    //var movies = await aDbContext.Movies
    //                .WithSpecification(byReleaseYear)
    //                .ToListAsync();

    return Results.Ok(movies);
});

app.MapPost("/movies", async (Movie movie, Repository<Movie> repo) =>
{
    await repo.AddAsync(movie);
    await repo.SaveChangesAsync();
    // Or, simply with a DbContext:
    //await aDbContext.Movies.AddAsync(movie);
    //await anyDbContext.SaveChangesAsync();

    return Results.Created($"/movies/{movie.Id}", movie);
});

app.Run();

public class MoviesByReleaseYear : Specification<Movie>
//           ^^^^^
{
    public MoviesByReleaseYear(int releaseYear)
    {
        Query
            .Where(m => m.ReleaseYear == releaseYear)
            .OrderBy(m => m.Name);
    }
}

public record Movie(int Id, string Name, int ReleaseYear);

public class Repository<T> : RepositoryBase<T> where T : class
//           ^^^^^
{
    public Repository(MoviesContext dbContext) : base(dbContext)
    {
    }
}

public class MoviesContext : DbContext
{
    public MoviesContext(DbContextOptions<MoviesContext> options)
        : base(options)
    {
    }

    public DbSet<Movie> Movies { get; set; }
}

Ardalis.Specification provides a RepositoryBase<T> class that wraps our DbContext object and exposes the database operations using Specification objects. The ListAsync() receives a specification, not an IQueryable object, for example.

Our Repository<T> is simply a class definition without query logic. Just a couple of lines of code.

Now the query logic is inside our MoviesByReleaseYear. Ardalis.Specification translates those filtering and ordering conditions to the right chain of Entity Framework Core methods.

Our repositories are way simpler and the query logic is abstracted to another object.

A Naive Implementation of the Specification Pattern

The best way to understand a piece of code is to recreate a minimal version.

Here’s a naive and bare-bones implementation of an in-memory repository that filters a list using a specification object:

var movies = new[]
{
    new Movie("Terminator 2", 1991),
    new Movie("Totoro", 1988),
    new Movie("Saving Private Ryan", 1998)
};
var repo = new InMemoryRepo<Movie>(movies); // <--
var found = repo.List(new ByReleaseYearSpec(1998)); // <--
// Output:
// [ Movie { Name = Saving Private Ryan, ReleaseYear = 1998 } ]

public record Movie(string Name, int ReleaseYear);

public class ByReleaseYearSpec : Spec<Movie> // <--
{
    public ByReleaseYearSpec(int releaseYear)
    {
        Where(movie => movie.ReleaseYear == releaseYear);
    }
}

public abstract class Spec<T> // <--
{
    public Func<T, bool>? Predicate { get; set; }

    public void Where(Func<T, bool> predicate)
    {
        Predicate = predicate;
    }
}

public class InMemoryRepo<T>
{
    private readonly IEnumerable<T> _items;

    public InMemoryRepo(IEnumerable<T> items)
    {
        _items = items;
    }

    public IEnumerable<T> List(Spec<T> spec)
    //                    ^^^^  
    {
        var evaluator = new SpecEvaluator();
        return evaluator.ApplySpec(_items, spec).ToList();
    }
}

public class SpecEvaluator
{
    public IEnumerable<T> ApplySpec<T>(IEnumerable<T> items, Spec<T> spec)
    {
        // The original implementation uses some "evaluators" to:
        // 1. Check if the spec has a certain shape and 
        // 2. Apply that shape to the input "query"
        //
        // For example, WhereEvaluator checks if the spec has a Where clause and
        // applies it.
        // 
        // This SpecEvaluator would be a Composite of smaller
        // evaluators that look for a certain shape
        if (spec.Predicate != null)
        {
            return items.Where(spec.Predicate);
        }
                
        return items;
    }
}

All the magic is inside the InMemoryRepo and the SpecEvaluator classes.

In the original implementation, the SpecEvaluator takes the parameters from the filters inside our specification (like Where, OrderBy, Skip, and Take) and apply them using EntityFramework Core methods into the IQueryable object that represents our database query.

Voilà! That’s how to use the Specification pattern to make our repositories more SOLID. With the Specification pattern, our repositories have a slim interface and a single responsibility: to turn specifications into database calls.

For more C# content, check how to create test data with the Builder pattern, how to use the Decorator pattern with a real example, and how to use the Pipeline pattern: An assembly line of steps.

How to Handle the Startup Class When Migrating ASP.NET Core Projects

.NET 6.0 replaced the Startup class with a new hosting model and a simplified Program.cs file.

The Startup class is still available in newer versions. If we’re migrating a pre-.NET 6.0 project, the .NET upgrade assistant tool does the work while keeping the Startup class.

Here are 3 alternatives to handle with the Startup class when migrating to newer versions:

1. Official Docs Approach

Newer versions of ASP.NET Core work perfectly fine with the old Program.cs file and Startup class, we can choose to do nothing and keep them.

Here’s an old-style Program class:

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace AnOldStyleAspNetCoreProject;

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
         Host.CreateDefaultBuilder(args)
             .ConfigureWebHostDefaults(webBuilder =>
             {
                 webBuilder.UseStartup<Startup>();
             });
}

And a Startup class:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace AnOldStyleAspNetCoreProject;

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Nothing fancy. A simple API project.

If we want to keep the Startup class, here’s what Microsoft official docs show to make the Startup class work with the new hosting model and the simplified Program.cs:

var builder = WebApplication.CreateBuilder(args);

// vvvvv
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
// ^^^^^

var app = builder.Build();

startup.Configure(app, app.Environment);
// ^^^^^

app.Run();

We created a new instance of Startup inside the new Program.cs file.

2. Hybrid Approach

If we really want to ditch the Startup class, Andrew Lock recommends a hybrid approach in his blog:

Turn the methods from the Startup class into private methods in the new Program.cs file.

var builder = WebApplication.CreateBuilder(args);

ConfigureServices(builder.Services);
// ^^^^^

var app = builder.Build();

Configure(app, app.Environment);
// ^^^^^^

app.Run();

// This method used to be in Startup.cs
static void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

// This method used to be in Startup.cs too
static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

3. Do-It-Yourself Approach

And if we want our Program.cs to look like the newer ones, there’s no automatic tool for that (at least I couldn’t find one in my Googling session). We have to copy the contents of the Startup class into the Program.cs file, by hand:

var builder = WebApplication.CreateBuilder(args);

// Put here what you had in ConfigureServices...
builder.Services.AddControllers();

var app = builder.Build();

// Put here what you had in Configure...
app.UseAuthorization();

// Before:
//
// app.UseRouting();
// ASP0014: Suggest using top level route registrations instead of UseEndpoints
//app.UseEndpoints(endpoints =>
//{
//    endpoints.MapControllers();
//});
//
// After:
app.MapControllers();

app.Run();

We cut and pasted the content of Startup inside the right sections of the new Program.cs. This time, we get some warnings about deprecated methods and their alternatives.

Voilà! If you’re a lazy developer like me, do nothing or go with the approach from the official docs. Otherwise, go with any of the other two options.

Four Lessons and a Challenge for a Coder Struggling to Write

Last week, Syed, one of my email subscribers, shared his struggles with writing online.

Here’s an edited version of Syed’s email:

I wanted to start writing about my debugging journey of the things I had been stuck with long time and then solving it finally… But I couldn’t continue as I thought my website wasn’t the best with SEO and no one may read it on my website. Then I shifted to Twitter and later, due to fewer engagements, I couldn’t continue there either. Well no I think maybe writing for some well-known forums might be the way.

For Syed and you that want to start writing as a software developer:

Don’t Create, Document

Start by sharing what you do and what you learn. That’s a good start.

Writing online is like keeping a public time capsule. If you don’t know what to add to your time capsule, follow the 20-minute rule: if something takes you more than 20 minutes to figure out, write about it.

Write about your learning and the problems you’re solving at work. Probably, the next time you’re googling something, you will find your own writing. That’s magical.

If You Want to Start Writing, Don’t Start Your Own Blog

And don’t code your own blogging engine either.

When we want to start writing, we fall back to doing what we know best — coding — and start by coding a blogging engine. That’s where writing and blogging die.

The best place to start is on “social blogs.” Platforms for long-form content with readers and a distribution mechanism. This way, you don’t have to “chase” readers with SEO tricks and you’ll have faster feedback.

If you don’t know where to start, go with dev.to or Medium. I can’t recommend dev.to enough. It’s a beginner-friendly and welcoming platform for coders.

Once you start on a social blog, keep your blog as your main hub or a portfolio of your favorite posts. That’s what I do.

We All Started With Zero Readers and Followers

At the beginning, writing can feel lonely.

I wrote my first online piece in 2018 and nobody read it. Maybe only one or two of my coworkers. I saw my blog analytics going from ~10 views per month to eventually ~1000s in a matter of years. Yes, you read that right. Years.

Focus on writing your first 10 posts and keep trying and improving.

Here’s when the Show Your Work attitude keeps us writing in the long run. And like any other infinite game, you only lose if you stop playing.

Write for yourself and for sure others people will find it useful too.

SEO Is Another Skill to Master

If you go with a social blog, the platform does the SEO part for you. You don’t have to worry about it.

Search engines keep changing their rules with algorithm updates. These days, it seems Google favors Reddit posts instead of personal blogs.

You’ll be in a dead end if you try to chase every SEO update. Write for humans because search engines like it when you do that.

Instead of trying SEO tricks, go with these rules:

  1. Write to answer a query people might search for in a search engine.
  2. Make your posts easy to read for humans: Don’t use big chunks of text. Instead, use subheaders to break those big chunks of text.
  3. Link back to other posts using keywords: Don’t use “click here” or “see more.”

You see? Another skill to master. Go with a social blog.

Parting Thought and Challenge

I owe my career growth to a couple of skills: apart from coding, learning English and writing online.

After blogging for more than five years, writing online has opened doors here and there. I made my first money on the internet thanks to my blog, for example.

Even if you don’t make any money writing, it will give you clear thinking.

And if you’ve made it this far, here’s my challenge: create an account on dev.to and write your first 4 posts there and see where they take you. If you accept the challenge, contact me and share your first post.

Write as if no one is reading and then keep writing because you don’t know who’s reading.

Four Writing Lessons I Learned After Binge-Reading Herbert Lui's Blog

A Sunday evening free of dopamine ended up being a lesson on writing and blogging.

I started googling about note-taking and landed on Herbert Lui’s blog after finding his lessons after 800 Zettelkasten notes.

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.

Don’t start from scratch again. Go back to one of your old ideas or writings and give it a fresh lick of paint.

2. Prefer Quantity Over Quality

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.

That’s only one of the ideas I use to never run of writing ideas.

4. Don’t Write Masterpieces, Plant Seeds Instead

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.