Asking Better Questions
As a software engineer, it helps to be curious. You should be inquisitive and ask questions about the codebases, systems, processes, and procedures you work with on a daily basis. There is so much knowledge to be learned, and sometimes asking the right questions is the only way that wisdom will be passed down from more senior engineers to junior engineers. It is not only on you as a junior engineer to pass on information, as longer tenured engineers should be encouraging the keeping of documentation and knowledge resources current, but your teammates directly are some of the most valuable resources you will have during the course of your career.
Asking the Question
A curious software engineer might ask:
- Why do we spend so much time writing specs and designs or grooming our ticket backlog when we could be writing code?
- Why is our data processing service in the low code product a series of separate processing engines instead of one centralized processor?
- Why did we decide to use a builder pattern here instead of calling a method’s constructors directly?
(Among many other types of questions…)
Asking these kinds of questions not only helps you learn and understand how the systems around you operate, it also gives you context behind the decisions that were made in the past before you joined your team and why those choices were made, which will help you understand the reasons your team might do things in a certain way.
Making your Priorities and Expectations Known
When communicating with others, it is important that you also let your own priorities and expectations be known. This can mean adding in an expected response time or letting someone know how important a request is on your side.
Making this known to others without being extremely direct is a skill in and of itself. For example, if you would like someone to respond to your question by the end of the next business day, you could say:
“… can you help me take a look at this issue sometime today or tomorrow?” “… thanks for any aid you can give me as I would like to work this blocker out by tomorrow so I can keep moving forward on my development work for this sprint.”
Make Knowledge Sharing More Efficient
To ensure you are making the best use of your co-workers time, as well as your own, it is good to do your own research and brainstorming prior to asking your question(s).
As a junior engineer, it can be intimidating asking other engineers a question, especially if you’re just starting out in a new position. Half of the battle can be figuring out what to ask, and working that out requires a good understanding of the problem you are trying to solve. Once you know what to ask, you will also need to think about how to ask it and who you should ask.
It may be hard to find the right words to explain what a problem is, so it is important to take the time to understand the problem completely before asking for help.
If you don’t understand the problem well enough to explain it to someone else, then you need to take some time to review everything again. Reread the description of the task to make sure you are understanding it correctly. In some cases, it helps to write the problem down and the details you know surrounding it on paper first or type it out in your notes. Getting the thoughts out of your head will help you organize them, and sometimes, the solution will come to you during this time.
Consider the following:
Don’t: “I can’t figure out why this isn’t working. Can you help?” Do: “I can’t figure out why my web service call is not receiving a response. I’ve set up my service to make the call, it authenticates successfully as shown in the log output here: #insert log output# but I am not getting any response to the query afterwards. Can you help take a look to see if I’m missing something sometime this week?”
The second example conveys more information to the person helping you, and it gives them enough context to know what you are trying to do and where you need their help. Try to be specific in the details of your question, and the more context you can provide, the better.
When asking a question, taking some time to do the following will help you ask a great question:
- Narrow down the problem
- Do research and Search internal documentation/resources
- Reproduce the issue (and be able to do so live if possible)
- Give Context
- Ask the question
- Make your expectations known
Breaking Down Requirements
Try to break down requirements into technical implementations using the smallest pieces of individual features that you can. Try to think about your task in terms of the following requirements:
Requirements for Technical Implementations (Code and Logic)
What data do you have? (Inputs)
Determine what data you are putting into the system and what types of data being input are required, which ones are optional, and what the expected values or ranges should be for each input. This helps you dig deeper into what inputs you should expect so that you can build logic around them to prevent bad data from getting into your system. Thinking about the data being input and how it can be miss-used can help ensure security from the onset in your solutions.
Try to think in terms of how the system will handle different situations and permutations. You will naturally come up with some additional questions for your teammates or the product owner if you can think of edge cases where an end user or a service may provide some unexpected input.
Examples: The address field is only one input field in the design. What if someone only gives us their street name? What if the person lives in a country that does not have states? Should we break it into multiple fields so we can handle each one specifically? Are we verifying emails to make sure they are deliverable, or is basic email address format validation good enough? Will the input number in a data field always be an integer value? Or should we expect decimal values? What about negative numbers?
While the Appian Low Code Platform (LCP) makes additional data handling for basic data type manipulation easier by handling end user inputs, service-to-service transactions can be more error prone and can require more clarification around your inputs. The more robust your validation logic and error handling is, the better to prevent known and unknown issues from occurring.
Common errors occur when a program encounters unexpected inputs, so if you can ask good questions to clarify any missing requirements, you will be able to prevent errors before they happen.
What Data are you Returning? (Outputs)
Ensure the data you are returning from your program meets the requirements gathered and your overall design is solving the problem at hand.
Double-check what type or format your outputs should be in, especially if you expect the output to become the input into another program or service.
Here are some examples of what kinds of questions to ask (yourself and others) when clarifying requirements:
Should we output the data in CSV, JSON, or give the option for either? Does the data we are returning contain end user specific data (Personal Identifying Information)? If so, what are the security ramifications of this? Does our logic format or escape outputs automatically? How many results do you want in the API response? Should we paginate the results?
Error Handling
Reliable programs gracefully recover from certain errors and return helpful information to the end user or service if the program is unable to proceed.
In order to write code like this, you have to anticipate ways in which your program can fail. If you understand all of the things that can go wrong with your code, you can then add logic to handle those errors gracefully or to exit the program and display a helpful error message.
Here are more example questions related to error handling:
If the API request fails, how many times should we retry? Should we only retry for specific status codes? What error message should we display for each anticipated error being handled? Should I implement special error codes as well? Should I throw an exception if we hit any specific situations or events?
If you’re unsure of how to proceed when a block of code may fail, ask your coworkers or any stakeholders. This will often help clarify how you should handle errors in specific situations to meet the business requirements.
Business Needs
This comprises the core set of business requirements that determine how a program should store, modify, and delete data. This is typically established via the requirements gathered before development begins.
Making assumptions in business logic can lead to misunderstandings in how the business operates or make the program developed not completely solve the problem posed, so it is always important to clarify any ambiguity before proceeding. A small bug in your business logic can have enormous effects later on, whether issues are found right away in production or at a later date and time.
Here are a few example questions for clarifying business logic:
Should we use an end user’s email address as their user identifier? Or should we create a unique id in the system to denote that user in our data models? Should we send out alert messages via email? Or text message? Or a phone call? Should we differentiate which alerts or alert types can send out which type of communication? Should we disable a form after the user submits their inputs, or should we allow multiple submissions? Should we allow a user to save their inputs for later before making a final submission?
If you don’t have all the information you need to complete a task, you will need to ask for clarifications. It is a part of your responsibilities as a software engineer to find gaps in the requirements that affect the technical implementation and to ask questions that will clarify any ambiguities and fill in those gaps. The better you are able to fill in any missing requirements, the better software you will create, and that starts with communicating clearly to others when you are not sure how to handle specific scenarios in your code.
Working in an Existing Code Base
It can be a misconception of aspiring programmers that professional software engineers spend all of their time writing new code and building new systems from scratch.
This is actually far from the truth in the majority of cases. In fact, aside from planning, creating or contributing to designs, and writing documentation, most of your early days as a software engineer will be spent maintaining, extending, and fixing bugs in existing codebases. You will likely be tasked with making fixes or enhancements to code or systems that other engineers created.
Working on existing code bases gives you the opportunity to gain experience working on a mature codebase.
This allows you to get familiar with complex abstractions and business logic. There will be design patterns, coding standards, and test cases that the previous programmers established and that you’ll be able to follow when making your updates or changes. Following established policies and patterns when learning a new codebase will help you focus on the behavior of your code without adding in designing an overall implementation from scratch to meet a business need.
Learning via the existing codebase is especially important when you join a new team, because you will be learning the nuances of the codebase and the business rules while getting up to speed. Your manager will probably start you off with some bug fixes and enhancements before you graduate to larger projects. In many cases, it would actually be counterproductive for you to jump in and make large changes to a codebase that you don’t understand very well. That would be very risky, especially as a junior software engineer still learning the best practices and the business needs of the company.
Before you can run, you need to learn how to walk, which is why it’s so important to develop skills for reading and understanding unfamiliar code. The more quickly you can read code and understand its intended behavior, the more quickly you will be able to make changes, fix bugs, or identify edge cases that weren’t considered.
(In the next, final part of this series, I will cover some less technical, but critical skills and focus areas to take to help bridge the gap between a good software engineer and a great software engineer.)