I spent a significant portion of my summer mentoring interns at GitHub. In full Silicon Valley grandeur, the fleet of interns comprised of bright, ambitious students hand-picked from prestigious schools.
Working alongside them allowed me to hone in on common obstacles that impede new developers from quickly ramping up and making progress. It became painfully apparent that most of these challenges were preventable, but no formal guidance on “obvious” expectations was provided.
Junior-level software jobs expect basic familiarity with various development workflows, without recognizing the tremendous gulf between computer science degrees and corporate function. Not only are such capabilities expected to develop organically, but such assumptions result in organizational laziness, where the circulation of tribal knowledge stands in place of well-structured onboarding experiences. This gap, unfulfilled by even many large organizations, leaves new hires and interns unprepared for day-to-day challenges.
I noticed a pattern underlying questions repeated by interns. They all signaled developer workflow, Git, best practices, and general problem-solving sensibilities when working on a complex system. In response to their growing list of questions, I wrote up a list of “junior engineer pro-tips”—a combination of coding and non-coding skills that could accelerate their output and help them avoid common pitfalls.
Although the original write-up was published internally and far more specific to GitHub, I’ve generalized it for broader use after getting feedback that this sort of post was helpful. My goal was to provide a set of unstated but regularly applied software engineering guidelines to the benefit of those starting their careers.
Caveat: GitHub is a Rails shop, so much of the following is relevant to Ruby/Rails.
Accelerate your output
The habit of test-driven-development is incredibly useful. A codebase as massive as GitHub’s requires you to problem-solve with incomplete information about the system, meaning you’re bound to stumble upon quirky surprises, edge-cases, and idiosyncrasies. It’s typically useful to think through test cases and write tests before you write code to minimize costly re-writes and quickly validate a solution. Fostering this discipline helps you discover edge-cases in a more systematic way than stumbling through them haphazardly.
Testing tools and techniques:
Debuggers (such as
pry) will serve you profoundly. They open a console when you run a test and allow you to probe different objects, understand their state, look at what your code is returning, and develop a better understanding of expected vs. actual behavior.
Run relevant tests only. Depending on your test framework and accompanying gems (ex., guard-rspec, minitest-focus), there are ways to focus on executing a targeted set of tests instead of running every test in a particular file (for example, by dropping the statement “focus” above a specific test or set of tests). This enables you to concentrate your testing on what you care about and resultantly save time by only running relevant tests.
Triangulate from a variety of outputs: use a combination of the console, your server output depending on the problem you’re solving.
Server-side vs. client-side testing: learn when to use each, what types of tests to conduct in the browser vs. elsewhere (ex., this Chrome dev tools overview is good).
Security: make a practice of writing tests that protect against malicious attacks on your server resources and databases.
When to test
Here are some general rules that help you decide what to do first: write tests or application code.
- If the test is short/simple compared to application code.
- If the test is for the security model (since security is top priority).
- Whenever a bug is found, write a test to reproduce it and protect it against regressions, then write the application code to fix it.
- Write tests before refactoring code, focusing on testing error-prone code that’s especially likely to break.
- Generally, write controller and model tests first, and integration tests (testing functionality across models, views, and controllers) second. Integration tests are great because they allow us to simulate user behavior and test composite functionality.
Application code first:
- When the desired behavior isn’t clearly defined, or you’re prototyping, write application code first and then a test to codify behavior.
- Don’t write tests for code (such as detailed HTML structure) likely to change in the future.
- Skip testing when code isn’t brittle or expected to change.
- When you are testing server-side code: you are testing the logic and readiness of your application.
- When you are testing client-side code: you are doing end to end testing.
GitHub uses MiniTest. The anatomy of a test will typically follow this template:
Refer to the Rails testing guide for more thorough documentation on MiniTest and assertions. If you are courting the idea of building a Rails app yourself, be sure to check out RSpec, another popular testing framework.
2. Keyboard shortcuts and Linux commands
Development necessitates navigating between your editor, terminal, and multiple browser tabs. Keyboard shortcuts allow you to manipulate your digital space quickly. This makes context-switching easier, which is necessary given how modern development workflows integrate the simultaneous use of many tools to solve a problem. Engineers are regularly racing against their short-term memory to hold onto information from multiple sources (ex., the server output, the editor, StackOverflow, and other tabs, etc.) while keeping track of the problem to be solved. This requires incredible focus. Your tools are imperative in enabling that focus.
Learn Enough Command Line to Be Dangerous is an excellent resource to lean on.
3. Git and workflow
The following questions were repeated a lot:
- “How many commits should my branch have?”
- “How big should a commit be?”
- “What’s a good commit message?”
There are accepted practices around this stuff. Rather than replicating existing wisdom here, I’d refer you to an interactive CodeSchool course on Git.
4. Make your workspace pretty
De-clutter your screen
An organized, decluttered digital workspace goes a long way in helping you think. SizeUp supports keyboard shortcuts that can help you keep your screen clean and less cluttered.
Soup up yo bash profile
You can customize your prompt with colors and information that’s most convenient for you may help. @mdo has a pretty great one that might be worth borrowing from as a baseline and adapting to your own needs.
Soup up yo editor
Depending on what you’re using, you can optimize it to do more heavy-lifting for you. Here are some neat Atom packages that may be of interest.
Systems thinking and decision making
There are often several different ways to do something. Software development principles are often subjective; they depend on a given problem, its constraints, and the ideological beliefs of the team and organization. This process is never a precise science, but there are philosophies to lean on and use as guardrails when deciding where to put your code, how to test it, etc. The following resources illuminate some of those less-empirical and more heuristical guidelines:
- Clean Code has some good philosophies that may help you evaluate which class to put a method in, or how closely to couple behavior.
- Thinking in Systems is a fun non-technical read that helps orient your mind for complex systems-thinking.
- Jepsen is an excellent resource for distributed systems and databases.
- POODR: Practical Object-oriented Design in Ruby is a favorite amongst many.
6. Confidently asking questions
Part of sound engineering practice is clarifying requirements and expectations upfront. However, asking questions can be intimidating, and presupposes a level of inner-conviction that is difficult to have early in your career.
When asking questions or seeking help, it’s generally useful to share what problem you’re solving, why you’re solving it, and stating the information you already know. This allows the other person to quickly build context, in addition to showing what you’ve investigated already, narrowing the scope for a more specific conversation.
How to say it at work: a book on how to frame things appropriate for a professional context.
Choosing a medium: learning to distinguish between the types of communication that’s best over Slack/in-person/in a pull request, issue, email, video-conferencing, etc.
Write to strengthen intuition: I still struggle with being vocal about my ideas without rigorously testing them first, an unsustainable habit born of insecurity. To build confidence, I document thoughts and keep a tally of when my intuition was correct or incorrect. If the idea was later validated, I try to understand why. This exercise allows me to pick up on patterns and better build connections that close the gap between explainable intuition and concrete technical knowledge.
7. Comfort with inactivity
While counterintuitive, taking a step back to scope out a problem, do research or ramp up on a new area is sometimes more important than jumping into code. There’s an industry bias that falsely conflates this outside-the-code-reasoning with laziness. A natural inclination to feel productive can lead you into a trap of writing suboptimal code without having a robust blueprint or plan in place. The cost of such eagerness can be immense and may require one to backtrack due to improperly assessed criteria.
There’s, of course, a balance here to keep your productivity in check; using the following guideposts is typically helpful:
- Ensure your manager/team-mates know what you’re doing and why you’re spending extra time on something.
- Make sure no one is blocked.
- Get feedback early. For example, when investigating creative fancies that lead you to implement entirely-new-and-inconsistent-but-super-interesting design patterns, a good manager can guide you away from what may be a waste of time.
- If you’re stuck on a problem for 20+ minutes, ask someone for help.
8. Learning together
Internships benefit from a type of camaraderie that’s difficult to replicate once you enter the workforce full-time. Use this unique closeness to develop a support system. Start reading lists, book clubs, or collaborate on side-projects. Take advantage of the incredible cohort you’ve joined.
Ending with a note of gratitude
I feel lucky to have mentored and learned from GitHub’s 2017 interns. Beyond their commitment to building technology, it was refreshing to see their genuine devotion to critically engaging in our industry’s cultural aspects. Thank you for your passion for developer tools and evolving our standards for diversity.