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.
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.”
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.
usingArdalis.Specification;// <--usingArdalis.Specification.EntityFrameworkCore;// <--usingMicrosoft.EntityFrameworkCore;varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddScoped(typeof(Repository<>));// <--builder.Services.AddDbContext<MoviesContext>(options=>options.UseInMemoryDatabase("MovieDb"));varapp=builder.Build();app.MapGet("/movies/{releaseYear}",async(intreleaseYear,Repository<Movie>repo)=>{varbyReleaseYear=newMoviesByReleaseYear(releaseYear);// <--varmovies=awaitrepo.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();returnResults.Ok(movies);});app.MapPost("/movies",async(Moviemovie,Repository<Movie>repo)=>{awaitrepo.AddAsync(movie);awaitrepo.SaveChangesAsync();// Or, simply with a DbContext://await aDbContext.Movies.AddAsync(movie);//await anyDbContext.SaveChangesAsync();returnResults.Created($"/movies/{movie.Id}",movie);});app.Run();publicclassMoviesByReleaseYear:Specification<Movie>// ^^^^^{publicMoviesByReleaseYear(intreleaseYear){Query.Where(m=>m.ReleaseYear==releaseYear).OrderBy(m=>m.Name);}}publicrecordMovie(intId,stringName,intReleaseYear);publicclassRepository<T>:RepositoryBase<T>whereT:class// ^^^^^{publicRepository(MoviesContextdbContext):base(dbContext){}}publicclassMoviesContext:DbContext{publicMoviesContext(DbContextOptions<MoviesContext>options):base(options){}publicDbSet<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:
varmovies=new[]{newMovie("Terminator 2",1991),newMovie("Totoro",1988),newMovie("Saving Private Ryan",1998)};varrepo=newInMemoryRepo<Movie>(movies);// <--varfound=repo.List(newByReleaseYearSpec(1998));// <--// Output:// [ Movie { Name = Saving Private Ryan, ReleaseYear = 1998 } ]publicrecordMovie(stringName,intReleaseYear);publicclassByReleaseYearSpec:Spec<Movie>// <--{publicByReleaseYearSpec(intreleaseYear){Where(movie=>movie.ReleaseYear==releaseYear);}}publicabstractclassSpec<T>// <--{publicFunc<T,bool>?Predicate{get;set;}publicvoidWhere(Func<T,bool>predicate){Predicate=predicate;}}publicclassInMemoryRepo<T>{privatereadonlyIEnumerable<T>_items;publicInMemoryRepo(IEnumerable<T>items){_items=items;}publicIEnumerable<T>List(Spec<T>spec)// ^^^^ {varevaluator=newSpecEvaluator();returnevaluator.ApplySpec(_items,spec).ToList();}}publicclassSpecEvaluator{publicIEnumerable<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 shapeif(spec.Predicate!=null){returnitems.Where(spec.Predicate);}returnitems;}}
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.
.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.
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:
Turn the methods from the Startup class into private methods in the new Program.cs file.
varbuilder=WebApplication.CreateBuilder(args);ConfigureServices(builder.Services);// ^^^^^varapp=builder.Build();Configure(app,app.Environment);// ^^^^^^app.Run();// This method used to be in Startup.csstaticvoidConfigureServices(IServiceCollectionservices){services.AddControllers();}// This method used to be in Startup.cs toostaticvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv){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:
varbuilder=WebApplication.CreateBuilder(args);// Put here what you had in ConfigureServices...builder.Services.AddControllers();varapp=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.
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:
Write to answer a query people might search for in a search engine.
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.
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.
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.