Drab to Fab: Makeover Your Project README In 3 Steps

Oh yeah! We’re giving your README a makeover!


The README will be one of the first things someone sees when they view your project. Think of it as the subject line to your email, or the elevator pitch to your presentation. The README is the opportunity to get someone interested in your open source project, or to convince them that you’re an awesome developer or simply to show off the cool project you’ve been working on in your spare time.

Yet, sometimes the README is forgotten. I get it, the code just needs to work sometimes. But, I highly encourage you to take a look at your project’s READMEs and iterate on them.

Here are 3 areas to focus on when updating or creating your project READMEs.

Describe Your Project


I know, this probably sounds obvious, but you would be surprised. Dig deep enough into my Github profile and you’ll find a few repos with the default, outdated content, or—gasp—no READMEs at all. Writing about your work is a whole, big topic I’ll go into detail on another time. But, for your README, it can be straightforward.

Some questions to ask yourself when writing your project description include:

  • What technologies does this project use?
  • How can someone else use it?
  • Why was it created? Or, what problem does it solve?
  • Is this repo part of a bigger project or does it exist on its own?

Overall, a few lines explaining what your project is, what you used to build, and why, may be all you need to include. Think of the description as a concise, one-paragraph summary to your project to entice someone to know more.

Installation Instructions

Raise your hand if you’ve come across a README that had vague installation instructions!


To help save someone else from experiencing this, with my projects, I like to take a literal step by step approach to getting it up and running locally. I’ll start by doing a fresh clone of my repo on my Desktop or anywhere other than where I usually work on the project locally.

Then, write down exactly the next step is to getting it up and running. Include if there are any packages to include or API keys to fill in or if a certain command needs to be run in a specific directory. I like including the exact terminal commands in tick marks, for example git clone https://github.com/scrabill/cactus-buddy, for easy copy and pasting from the README.

Link To a Live Version

This may not apply to all projects, but it’s something to think about. If the goal of creating personal projects is to have something to show potential employers, you cannot always expect someone to be able to—or have the time to—clone it to their machine, get it up and running and then navigate the codebase.


In my bootcamp experience, a stretch task was to have the portfolio project built deployed online so that they can be used/viewed in the browser. Not only does this make a project easier to look at, experience, evaluate by others, but it also shows additional skills at being able to use cloud applications like Netlify, Heroku or Amazon Web Services.



If you can, I encourage you to take a critical eye at your own READMEs and do an audit. Put yourself in the shoes of someone who is coming across your project for the first time, with no context on what it’s about.

Some things to think about:

  • Does your README accurately explain what your project is?
  • What about how you built it?
  • If the project is open to contributions, does the README clearly state that?
  • Are the installation instructions missing any gaps or not work as expected?

Consider getting a friend, technical or non-technical, to take a look at your README and offer their initial thoughts. Other developers will likely be seeing your project, but so may potential hiring managers and recruiters. They may only have a few seconds to look at your project’s README to evaluate if you have the technical and communication skills they are looking for in an individual contributor.

What updates will you make to your README today?


Amending Git Commits

A best practice when using version control like git, is that commits should be small and relate to one aspect of your codebase. For example, a commit should include a correction for one function, in one part of your app, and not an update to a function, a new fetch request, and documentation changes. Think about it this way. If you had to revert or correct a change in a commit that touched many files or many aspects of a project, it would take more work and increase the likelihood of errors.

Similar to writing functions with a single responsibility, your commits and commit messages should equally concise.

While making some updates to a project of mine, I made the mistake of lumping two groups of code changes into one commit. Since this is a personal project, that likely I will only work on, it’s not the end of the world. However, I try to follow best practices whenever I can. Previously, if I lumped to much code into one commit. I’d make a new commit, reverting the changes (or, what changes I could remember 😬). Then, make a new commit, with part one of the changes I made the first time. Then another commit with part two of the changes.

This is messy and again, creates room for error.

Fortunately, there is an easier way.


git commit –amend

With the command `git commit –amend` you can do two things.

  1. Update the message associated with the most recent commit
  2. Update the content associated with the most recent commit

I’ll walk through how to do the latter, although the steps are similar.

Note. Your terminal set up or exact commands may look different, depending your your computer set up. I use a Mac OS, Bash Terminal and either Atom or Visual Studio Code for editing files.

Oops! Too much code.

In my scenario, I was making some updates to the CSS of my person website. I replaced any hard coded hex values with CSS variables and adding some smooth scrolling behavior. Here’s a simplified version of my “oops” commit with too many code changes.

root {
     --primary-color: #981ceb;
     --secondary-color: #111111;}

html { 
     scroll-behavior: smooth;

a, a:visited, a:hover, a:active { 
     border-bottom: 1px solid var(--primary-color);
     color: var(--primary-color);
     text-decoration: none;

That scroll-behavior attribute shouldn’t be there! But it’s ok, we can amend this commit, get rid of that extra code and keep our commit history nice and clean.

Updating the Commit

First, in the same branch as the “oops” commit, make your changes. In my case, I’ll remove the `scroll-behavior` line. My CSS file now looks like this.

root {
     --primary-color: #981ceb;
     --secondary-color: #111111;}

a, a:visited, a:hover, a:active { 
     border-bottom: 1px solid var(--primary-color);
     color: var(--primary-color);
     text-decoration: none;

Note. At this step, you can also add code (or files) that you forgot to add to the previous commit.

Next, in your terminal, run git commit --amend which will drop you into the commit editor. If you haven’t seen it before, there can be a lot going on. Mine looked something like this.

Oops - This commit has too much code in it. 

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Date:      Sat Aug 15 00:02:16 2020 -0400
# On branch master
# Changes to be committed:
#            modified: styles.css#

If you want to keep the commit message the same, hit esc then type :wq to exit the editor and go back to your terminal.

That’s it!

If you type git log you will see your commits, with the most recent one—the one you just corrected—on top.

Time Travel is Tough


This is the very top of the iceberg of what rewriting history in git can look like. Always proceed with caution, especially in a larger project that other developers may be working on, forking, etc.


Cover photo by Charles Deluvio on Unsplash

Nominee: Ruby on Rails Thinker of the Year

I was happily surprised to see that I was nominated as a Ruby on Rails Thinker of the Year in the Hacker Noon second annual Noonie awards.

What is Hacker Noon? What is a Noonie?

Hacker Noon is an online technical publication featuring 12,000+ contributing writers (including myself), high-quality content, and—best of all—no paywalls!

The Noonies are the Hacker Noon awards. Described as a “…dundies – but for technology”, the Noonies aim to reward the underrepresented corners of the internet. More information behind Noonie can be found in their FAQ.

Vote for Me!

Starting today, voting is open! It only takes a few seconds, does not require a login or Hacker Noon account*.

Here’s how to do it.

  • Navigate to the Ruby on Rails Thinker of the Year page
  • Find my name, Shannon Crabill, in the list
  • Click the Vote button to the right of my name
  • Wait 24 hours and repeat.

*While a Hacker Noon account is not required (unlogged in users can vote) your vote counts as 3 if you are an authenticated user. If you are not logged in, your vote counts as 1. So, if you can create and verify an account, and vote, your vote goes a little bit further. Additional information can be found in the FAQ.

You can vote once per day, per award (I highly recommend checking out the other nomination categories too) until October 12th, 2020

And that’s it!

If social media is your thing, you can support me by doing the following.

  • Share this blog post
  • Share, retweet, comment on, like, etc my Twitter or Linkedin post announcing my nomination.
  • Sharing the award page on social media. You will also see a prompt to share after voting

Hashtags to use include: #Noonies, #Noonies2020, #HackerNoon

Ruby & Rails & Me

I learned Ruby, then Ruby on Rails in 2019 while enrolled in the Flatiron School Software Engineering Bootcamp. It felt so forgiving compared to other programming languages.

While I’ve always seen myself as a front-end person verse back-end, I also enjoy building games and fun web apps, which usually require a backend database. I like to go the low-cost route, which means building a backend that works when I can. Luckily, Ruby on Rails makes it relatively straightforward to go from an empty directory to a backend server ready for some data.

What I’ve Built

By Ruby on Rails work extends to personal projects, which include:

  • Food or Foe? An emoji matching game with Ruby on Rails as an API.
  • Central Perk. A coffee shop point-of-sale application.

I’ve also written about my projects and experiences with Ruby on Rails:


Approaching Problems like a Software Engineer

During a recent, mock technical interview, I was given the following problem to solve.

Write a function that takes in two inputs, an array, and a number. Remove the first item in the array. Then, move it to the last place in the array. Repeat this the number of times as given in the second argument and return the array.

For example, if the given array is [1,2,3], and the number of times it should shuffle is 1, the function should return [2,3,1].

Break Down the Problem

Given the problem, I thought about the main components (or problems) I would need to make things work.

  • Remove the first item in the area (the item at place array[0])
  • Take the removed item and place is at the end of the array (it becomes the item at place array[array.length -1].
  • Repeat the above for each time the second agreement specified
  • Return the shuffled array

Knowing that the act of removing an item and placing it into the array would have to happen x numbers of times, a for loop seemed like a good place to start. Within that loop I could remove the first item in the array with .shift() then add it back onto the end of the array with .push().

Putting that together into a function, our solution could look like this:

function moveToEnd(array, number) {
	for ( let i = 0; i < number; i++ ) {
		let itemToMove = array.shift()
	return array

Given the following inputs:

moveToEnd([1,2,3], 1)
moveToEnd([1,2,3], 2)
moveToEnd([1,2,3], 3)

We get the following outputs:


It works!

While this solution does work, it may not be the best way to approach the problem. Let’s think about some possible edge cases that we have not accounted for.

Run Time

In the sample code, I have three simple examples of what the inputs and outputs could look like. The array is relatively small, so have been the number of times the for loop needs to run.

In Javascript, an array can be any size. Shuffling through an array of 1000 items would take longer than shuffling through an array of 3 items. An array of 1,000,000 items would take even longer. Alternatively, if a 3 item array needed to be shuffled 1,000,000 times, it would take longer than removing an item from the front and placing it on the end 3 times.

Time complexity, runtime, and Big O notation are some of the concepts in computer science surrounding the idea of how fast, slow, or performant a program or function is. These concepts are too complex to cover in this blog post, however, we can keep the idea of runtime in mind as we refactor the moveToEnd function.

Noticing Patterns

Focusing on one aspect of the function, the user inputs, we can improve our solution a little bit.

Going back to our earlier examples, let’s shuffle the [1,2,3] array a few more times.

moveToEnd([1,2,3], 1) // expected return [2,3,1]
moveToEnd([1,2,3], 2) // expected return [3,1,2]
moveToEnd([1,2,3], 3) // expected return [1,2,3]
moveToEnd([1,2,3], 4) // expected return [2,3,1]
moveToEnd([1,2,3], 5) // expected return [3,1,2]
moveToEnd([1,2,3], 6) // expected return [1,2,3]

You may see a pattern here.

If the array is shuffled 1 time or 4 times, the resulting array ([2,3,1]) is the same. Each time number is incremented by 1, the resulting array is the same as every 3rd instance. Another interesting thing is that when the array is shuffled 3 times (or a multiple of 3 with 0 as a remainder) the resulting array is the same as the original array.

This is interesting to think about.

If the return of a function is the same when we shuffle the array 1 time, 3 times or 6,000,000 times, do we really need our for loop to run 6,000,000 times?


We can make our code a little more performant by simplifying our number to be as small as possible in comparison to the array then running the for loop that many times.

Here are some scenarios to think about:

  • If number is less than then the length of the array run the for loop
  • If number is greater than length of the array, divide it by the length of the array, then use the remainder to run the for loop

There are a few ways to tackle this, but for the purpose of this example, I’ll use an if statement to evaluate the value of the number argument.

	if (number > array.length) {
		number = number % array.length

On the second line, we use the modulus operator to return the remainder and assign it to the number variable, that is then used in the for loop.

Our updated function now looks like this.

function moveToEnd(array, number) {
	if (number > array.length) {
		number = number % array.length
	for ( let i = 0; i < number; i++ ) {
		let itemToMove = array.shift()
        return (array)

With this addition, our for loop will run a relatively small number of times compared to what the number argument could be.

What else?

The moveToEnd problem is relatively easy to solve on purpose. Solving problems as an engineer is part of getting to a solution that works. And part getting to a solution that scales, accounts for edge cases or has minimal trade-offs or side effects.

In technical interviews, you may not have all the answers to what inputs could look like, what outputs should be, or other factors to consider. But, that’s on purpose. It’s on you, as the developer, to ask clarifying questions to help guide you to an optimal solution.

For example, when I was posed with this problem, I asked if it was ok to mutate the original array, or if I should return a new, shuffled array, leaving the original intact. If I was approaching this problem now, I may think about if my solution works with a nested array. Or, to account for if the number argument is not an integer or less is less than 0. Going a little deeper, I may revisit the for loop to see if there is a better option.


Again, moving items in an array may not be a scenario you will encounter in the real world. But, thinking about making your code efficient is something you will encounter. Challenge yourself to revisit code you have written and refactor it to account for less-than-ideal user inputs, time complexity any other edge cases you can think of.


Cover photo by Olav Ahrens Røtne on Unsplash

Custom Data Attribute

You may have come across an attribute that begins with data- in the HTML code of a website or tutorial.

What are those attributes and what do they mean?

Data Attributes, as they are called are a semantic and accessible way to label HTML elements. Instead of interacting with an element by its CSS ID, class, or value, you can create custom data attributes and style and interact with those elements using its data attribute.

Let’s look at an example.

If you had a board for a game, where each square or space is represented by a div, most likely you would need a way to identify one div from another, so that write logic for how the game works, how pieces can move, etc.

To create our board in Javascript, our function may look something like this.

    function createBoard() {
      for (let i = 0; i < boardSquareArray.length; i++) {
        let square = document.createElement('div')

        // boardContainer is a parent div, that contains the boardSquare divs

We have a loop that is creating a div an element as many times as there are elements in the boardSquareArray. Since we are incrementing the value of i each time the loop runs (and stopping once i is equal to the length of boardSquareArray) we could use it as a data-id attribute for each board square.

Updating our code above, we now have something like this:

   function createBoard() {
      for (let i = 0; i < boardSquareArray.length; i++) {
        let square = document.createElement('div')

        square.setAttribute('data-id', i)

        // boardContainer is a parent div, that contains the boardSquare divs

Assuming our boardSquareArray as a length of 5, our HTML will look like this:

<div class="grid">
    <div data-id="0"></div>
    <div data-id="1"></div>
    <div data-id="2"></div>
    <div data-id="3"></div>
    <div data-id="5"></div>

Arrays are zero-indexed, so our first div has an data-id value of 0. If it needed to start with 1, we can make this update to the setAttribute.

square.setAttribute('data-id', i + 1)

Ok, so?

With the above example, you may be wondering why not use id instead of data-id? In this case, yes, id does make more sense, since data attributes should only be used when there is not a more appropriate HTML attribute to use.

A real-world example of where a custom data the attribute is useful is for specifying a dark or light theme within the CSS.

If the dark theme was activated by a checkbox, an attribute like data-theme with a value of dark could differentiate which CSS to apply to the page. Here is a quick example.

See the Pen Data Attribute for Theme Switching by Shannon Crabill (@scrabill) on CodePen.

But why not use a class?

Right! This same effect could be triggered by adding and removing a dark class to the body tag and using that to change properties in the CSS. But, think about if we’re rendering more than a line of text to the page. What if a webpage needed to render data from multiple objects onto a page?

For an online marketplace, one object may have dozens if not hundreds of attributes (or properties) associated with it. The code would quickly become unmanageable if each attribute had corresponding CSS classes or IDs and those were used for styling or sorting and filtering with Javascript.


I think data-attributes are really cool!

I can see how they can be used to write cleaner, more accessible code, especially for data-heavy applications.