The Fallacy of “Years of Experience” in Software Development

In the ever-evolving landscape of software development, the metric of “years of experience” has long been considered a gold standard for assessing a developer’s expertise. However, as the industry matures and the complexity of technology deepens, it’s becoming increasingly clear that this measure is not only outdated but often misleading. Let’s dive into why “years of experience” should no longer be the primary gauge of a developer’s prowess and explore what truly reflects a developer’s skill and impact.

The Irrelevance of “Years of Experience”

  1. Experience Doesn’t Equate to Expertise: Merely clocking in years at a job does not guarantee a deep understanding or mastery of software development. A developer with ten years of repetitive, unchallenging tasks may know far less than a developer with three years of intense, varied projects. Quality and diversity of experience eclipse the sheer passage of time.
  2. Rapidly Changing Technologies: The tech world is in a constant state of flux. Programming languages, frameworks, and best practices evolve at a breakneck pace. A developer who hasn’t kept up with these changes may find their once-relevant skills becoming obsolete. It’s not about how long you’ve been coding but how well you’ve adapted to and adopted new technologies.
  3. Innovation and Problem-Solving Over Time Served: Software development is fundamentally about solving problems and innovating. A developer who consistently finds creative, effective solutions and can bring them to fruition is far more valuable than one who simply logs hours. Innovation is not a function of time; it’s a function of mindset and approach.

Better Measures of a Developer’s Skill

  1. Successful Solutions Deployed: A more meaningful measure of a developer’s capability is the number and impact of successful solutions they’ve deployed. This includes applications, systems, and features that solve real-world problems, meet user needs, and provide business value. A track record of successful deployments speaks volumes about a developer’s practical skills and effectiveness.
  2. Longevity and Iteration of Solutions: For developers with 5+ years in the field, an excellent metric is the longevity and continuous improvement of their solutions. How many of their deployed solutions are still in production and actively used? More importantly, are these solutions being iterated on and enhanced, either by the original developer or by others? This indicates not only the robustness and relevance of their work but also their ability to build maintainable, scalable systems.
  3. Breadth and Depth of Projects: Evaluating the variety and complexity of projects a developer has tackled provides insights into their adaptability and breadth of knowledge. Developers who have worked across different domains, tackled diverse challenges, and delivered under varying constraints are likely to have a richer, more versatile skill set.
  4. Community and Contribution: Active participation in the developer community through open-source contributions, technical blogging, mentoring, and speaking engagements can also be a strong indicator of a developer’s passion and expertise. These activities demonstrate a commitment to continuous learning and sharing knowledge, which are crucial traits for staying relevant in the tech industry.

The Path Forward

As we rethink how we assess software developers, it’s crucial for hiring managers and team leads to shift their focus from years of experience to more meaningful metrics. By valuing successful solutions, the longevity and iteration of these solutions, and the breadth and depth of project experience, we can better identify truly skilled and impactful developers.

In practice, this means asking different questions during interviews, emphasizing problem-solving and project-based assessments over resumes and tenures. It’s about fostering an environment where continuous learning, adaptability, and innovation are prioritized and rewarded.

In conclusion, while “years of experience” has been a convenient shorthand, it’s time we move beyond it. Let’s embrace a more nuanced, accurate understanding of what makes a developer truly great. Because at the end of the day, it’s not about how long you’ve been in the game; it’s about how well you implement and play it.

The Reality of “Code Challenge” Culture Outcomes Among Large & Small Organizations

Key definitions for this article:

“Code Challenge” Culture: This culture is more of a “do as we say, we’ll grow you as we see fit, and you may then continue with us if you perform accordingly” type of environment. It starts with a more defined, strict adherence to an academic achievement type environment that enforces one do things a certain way and follow a certain path, and to ensure you too will do those things a code challenge is used to gate keep a particular job role. There are obviously good and bad results to this culture, and this article is going to get deep into those cultures.

Wholistic Culture: This culture is more open to getting a larger picture idea of a candidate in the interview pipeline. With more of an intent around having the individual develop the organization and for the individual to learn. The possibility of bringing knowledge that can help one improve the organization is readily accepted as is an individual that might not pass a rigorous academic code challenge to shift arrays, or move bits, or trickle values around a binary tree! There are however, good and bad outcomes to this type of culture.

Caveats: In both cultures there are numerous additional criteria that changes them. In this article I’ll write about them in a general sense, and then when detailing something specific, I’ll state that caveat with a call out.

The Big Organizations & Smaller Niche Organizations

I’m going to use three big companies and three small companies here that have varying degrees of “Code Challenge” Culture. In that, I’ll dive deeper into the problems that arise from a strict “Code Challenge” Culture vs. a Wholistic Culture.

Continue reading “The Reality of “Code Challenge” Culture Outcomes Among Large & Small Organizations”

A Guide Path for Strategic Growth and Leadership in Software Development

Growing a development team is somewhat parallel to building a skyscraper. You start with a solid foundation – you’d at least hope – add floors with meticulous planning, and eventually, you reach the pinnacle with strong leadership guiding the entire structure. In software development, this process involves strategic hiring decisions at various stages. Let’s delve into why focusing on Senior and Mid-Level developers initially is crucial, the role of Junior developers in the maturation phase, and the point at which hiring Principal developers becomes essential.

Continue reading “A Guide Path for Strategic Growth and Leadership in Software Development”

An Engineering Manager Challenge

This post came to mind while reading this post on LinkedIn, which is a share of this post by Kelvin Mai, and the follow up post Ted Neward made here. Read up on those for more insights into this topic.

Before diving into the details, it’s important to note that the criteria outlined here leave several questions unanswered, which could significantly impact the final decision. Factors like the specific nature of the projects, team dynamics, and long-term strategic goals are crucial and could sway the decision in different directions. That said, let’s break down the options.

Bringing on Two Junior Developers

Pros:

  1. Boosted Bandwidth: Two fresh faces mean more hands on deck to share the workload, increasing overall throughput.
  2. New Blood, New Ideas: Junior hires come with fresh perspectives and a ton of enthusiasm that can rejuvenate the team.
  3. Long-term Investment: Junior devs are like seedlings; with the right nurturing, they grow into valuable assets, contributing more as they develop.
  4. Flexibility: Juniors can be shaped to fit the team’s needs and culture, making them adaptable assets.

Cons:

  1. Boosted Bandwidth: Emphasis here though is on “throughput” above in the pros and not on committable, usable, and well written code you want and can use in the project. Increased throughput can be a negative, and IYKYK and can think of this as “induced demand” but for code.
  2. Training Time Sink: Juniors need a lot of guidance, which can be a major time drain for the senior members.
  3. Ramp-up Period: They won’t hit the ground running. Expect a learning curve before they start delivering at full capacity.
  4. Management Load: More people means more management. Even if they’re juniors, it adds to the admin overhead.

Hiring One Senior Developer

Pros:

  1. Expertise on Tap: A senior dev brings a treasure trove of knowledge and experience, perfect for tackling complex challenges and making strategic moves.
  2. Mentorship: They can mentor junior team members, leveling up the entire team’s skill set.
  3. Quick Impact: Seniors can dive in and start contributing much faster than juniors, thanks to their experience.

Cons:

  1. Higher Price Tag: Senior talent doesn’t come cheap. They’ll command a higher salary, impacting the budget more significantly.
  2. Cultural Fit Risks: There’s always the gamble that a senior hire might not mesh well with the existing team dynamics.
  3. Single Point of Failure: Relying on one senior dev for critical tasks can create dependencies, potentially leading to bottlenecks.

The “Do Nothing” Route

Pros:

  1. Save the Budget: Not hiring anyone saves money, which could be redirected elsewhere.
  2. Focus on Current Team: No new hires mean you can double down on developing and optimizing the current team.

Cons:

  1. Opportunity Cost: Leaving budget on the table means missing out on potential gains in capacity and productivity.
  2. Burnout Risk: Your current team might end up overworked, risking burnout and decreased morale.
  3. Stagnation: Without fresh talent, innovation can stagnate, and the team might struggle to keep up with growing demands.

The Decision

Let’s get real: not hiring anyone is likely the worst choice here. I know some would disagree in some situations, but if you’ve got budget allocated for new talent, use it! Even if the ROI isn’t immediate, it’s about positioning the team for long-term success. Hiring is an investment in the future—whether it’s two eager juniors ready to grow or a seasoned senior ready to lead and mentor. In this scenario, hiring one senior developer is the strategic move. You’ll get immediate expertise, mentorship for the juniors, and a stronger foundation for future growth. Let’s make that budget work for us and set the team up for success!

Addendum

This post attributed to leading me down the path to elaborate on staff and hiring details, including The Toxic Truth About Coding Challenges in Technical Interviews and then the elaboration on that post with The Reality of “Code Challenge” Culture Outcomes Among Large & Small Organizations.

Top 3 Refactors for My Hasura GraphQL API Terraform Deploy on Azure

I posted on the 9th of September, the “Setup Postgres, and GraphQL API with Hasura on Azure”. In that post I had a few refactorings that I wanted to make. The following are the top 3 refactorings that make the project in that repo easier to use!

1 Changed the Port Used to a Variable

In the docker-compose and the Terraform automation the port used was using the default for the particular types of deployments. This led to a production and a developer port that is different. It’s much easier, and more logical for the port to be the same on both dev and production, for at least while we have the console available on the production server (i.e. it should be disabled, more on that in a subsequent post). Here are the details of that change.

In the docker-compose file under the graphql-engine the ports, I insured were set to the specific port mapping I’d want. For this, the local dev version, I wanted to stick to port 8080. I thus, left this as 8080:8080.

version: '3.6'
services:
postgres:
image: library/postgres:12
restart: always
environment:
POSTGRES_PASSWORD: ${PPASSWORD}
ports:
- 5432:5432
graphql-engine:
image: hasura/graphql-engine:v1.3.3
ports:
- "8080:8080"
depends_on:
- "postgres"
restart: always
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:${PPASSWORD}@postgres:5432/logistics
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
volumes:
db_data:

The production version, or whichever version this may be in your build, I added a Terraform variable called apiport. This variable I set to be passed in via the script files I use to execute the Terraform.

The script file change looks like this now for launching the environment.

cd terraform
terraform apply -auto-approve \
-var 'server=logisticscoresystemsdb' \
-var 'username='$PUSERNAME'' \
-var 'password='$PPASSWORD'' \
-var 'database=logistics' \
-var 'apiport=8080'

The destroy script now looks like this.

cd terraform
terraform destroy \
-var 'server="logisticscoresystemsdb"' \
-var 'username='$PUSERNAME'' \
-var 'password='$PPASSWORD'' \
-var 'database="logistics"' \
-var 'apiport=8080'

There are then three additional sections in the Terraform file, the first is here, the next I’ll talk about in refactor 2 below. The changes in the resource as shown below, in the container ports section and the environment_variables section, simply as var.apiport.

resource "azurerm_container_group" "adronshasure" {
name = "adrons-hasura-logistics-data-layer"
location = azurerm_resource_group.adronsrg.location
resource_group_name = azurerm_resource_group.adronsrg.name
ip_address_type = "public"
dns_name_label = "logisticsdatalayer"
os_type = "Linux"
  container {
name = "hasura-data-layer"
image = "hasura/graphql-engine:v1.3.2"
cpu = "0.5"
memory = "1.5"
    ports {
port = var.apiport
protocol = "TCP"
}
    environment_variables = {
HASURA_GRAPHQL_SERVER_PORT = var.apiport
HASURA_GRAPHQL_ENABLE_CONSOLE = true
}
secure_environment_variables = {
HASURA_GRAPHQL_DATABASE_URL = "postgres://${var.username}%40${azurerm_postgresql_server.logisticsserver.name}:${var.password}@${azurerm_postgresql_server.logisticsserver.fqdn}:5432/${var.database}"
}
}
  tags = {
environment = "datalayer"
}
}

With that I now have the port standardized across dev and prod to be 8080. Of course, it could be another port, that’s just the one I decided to go with.

2 Get the Fully Qualified Domain Name (FQDN) via a Terraform Output Variable

One thing I kept needing to do after Terraform got production up and going everytime is navigating over to Azure and finding the FQDN to open the console up at (or API calls, etc). To make this easier, since I’m obviously running the script, I added an output variable that concatenates the interpolated FQDN from the results of execution. The output variable looks like this.

output "hasura_uri_path" {
value = "${azurerm_container_group.adronshasure.fqdn}:${var.apiport}"
}

Again, you’ll notice I have the var.apiport concatenated there at the end of the value. With that, it returns at the end of execution the exact FQDN that I need to navigate to for the Hasura Console!

3 Have Terraform Create the Local “Dev” Database on the Postgres Server

I started working with what I had from the previous post “Setup Postgres, and GraphQL API with Hasura on Azure”, and realized I had made a mistake. I wasn’t using a database on the database server that actually had the same name. Dev was using the default database and prod was using a newly created named database! Egads, this could cause problems down the road, so I added some Terraform just for creating a new Postgres database for the local deployment. Everything basically stays the same, just a new part to the local script was added to execute this Terraform along with the docker-compose command.

First, the Terraform for creating a default logistics database.

terraform {
required_providers {
postgresql = {
source = "cyrilgdn/postgresql"
}
}
required_version = ">= 0.13"
}
provider "postgresql" {
host = "localhost"
port = 5432
username = var.username
password = var.password
sslmode = "disable"
connect_timeout = 15
}
resource "postgresql_database" "db" {
name = var.database
owner = "postgres"
lc_collate = "C"
connection_limit = -1
allow_connections = true
}
variable "database" {
type = string
}
variable "server" {
type = string
}
variable "username" {
type = string
}
variable "password" {
type = string
}

Now the script as I setup to call it.

docker-compose up -d
terraform init
sleep 1
terraform apply -auto-approve \
-var 'server=logisticscoresystemsdb' \
-var 'username='$PUSERNAME'' \
-var 'password='$PPASSWORD'' \
-var 'database=logistics'

There are more refactoring that I made, but these were the top 3 I did right away! Now my infrastructure as code is easier to use, the scripts are a little bit more seamless, and everything is wrapping into a good development workflow a bit better.

For JavaScript, Go, Python, Terraform, and more infrastructure, web dev, and coding in general I stream regularly on Twitch at https://twitch.tv/thrashingcode, post the VOD’s to YouTube along with entirely new tech and metal content at https://youtube.com/ThrashingCode.