Towards a psychologically safe environment
Feeling in a safe environment is a prerequisite for a happy and productive team. I’ll mention a software team as an example but it could be applied to other teams.
Maslow’s hierarchy of needs is a structured pyramid of human needs.
Notice that personal security lies within the safety needs, just above the physiological needs. If we don’t satisfy those lower-level needs, how can we unleash the ones above, where creativity and innovation can happen? If you’re struggling to survive, how can you think about politics or culture? Studies show that psychological safety allows for moderate risk-taking, speaking your mind, creativity, and sticking your neck out without fear of having it cut off.
According to Wikipedia, “psychological safety is being able to show and employ one’s self without fear of negative consequences of self-image, status or career”. If you’re living in fear, whether because you’re afraid of breaking a system or of being judged, you’ll refrain from challenging the status quo or even participating. You may resort to finding ways to hide those fears that are nocive to your productivity and well-being. For example, if you didn’t understand something but you don’t say it out of fear, how unproductive is that? Ideally, you want to work at a place where it’s fine to show your weaknesses. Saying “I don’t know” should be a normal thing.
Fear and anxiety are always bad. Don’t normalize them. Taking risks is a requirement to evolve and making errors along the way is natural. If errors are part of life and work, we better learn to embrace them.
We learn from failure, not from success. Bram Stocker
The team should be mature enough to see mistakes as part of the learning process. You should feel safe to make mistakes with no feeling of guilt, regardless of your seniority level. Accepting the error is mostly about culture. It boils down to the people who make up the company and the team. With that in place, there’s autonomy to implement some practices and ways of working. Also, you can resort to technology in strategic ways (e.g. automation). Let’s explore these topics further.
Ways of working
Here, we include culture, practices, and methodology-related topics. There are too many to cover, so I’ll just highlight some that I find relevant and recommend that you investigate further:
- Proper welcome: you don’t need to throw a party when a new team member joins in, but make sure you prepare a few onboarding sessions. Be kind (remember when it was you). Pair programming can be a powerful welcome tool providing a sense of security and belonging. The typical alternative is to send lots of links and docs and say “tell me if there are questions”; of course, many people will refrain with fear of looking stupid.
- Focus: it’s proven that productivity is harmed by frequent switching; due to the stress involved, errors increase. Ideally, you should work on a single project/product at a time for a given period. Ideally, you should focus on a feature at a time and find ways to reduce interruptions.
- No finger-pointing and blaming: a team should be a cohesive group of people who help each other accomplishing a common goal. Competition is the opposite of that. Shared ownership a key strategy here — we’re all responsible for a product’s faults (and successes), and the focus is on understanding and avoiding repeating errors, not attributing blame.
- No gatekeeping: sentences that start with “a real programmer…” or “you don’t know that… ?” embody the adult version of bullying. Don’t make someone feel guilty for not knowing something, regardless of their experience. Use pair programming as a tool to foster learning and tackle know-how gaps.
- Don’t be a lone wolf. Resort to practices such as code reviews and pair programming to increase quality and know your colleagues. These practices reduce the fear of the unknown because we get the team’s feedback about us and our work. When onboarding someone, be open about your weaknesses from the start; mention your strengths later.
- Feedback culture: consider regular retrospectives, speedbacks, team health checks, and 1-on-1 feedback sessions (ad-hoc or scheduled) so that the team can increase trust in each other and continuously improve. Feedback is an immense topic that needs its own articles so make sure you read about it before implementing it.
- Experimenting culture: one way to reduce resistance to change is to make small experiments inspired by the scientific method: based on a hypothesis, propose an experiment, analyze the results, and act on that. This applies to anything — from tweaking the technology to adapting the methodology. This reduces the fear of commitment because we can try (small) things before. It’s also a way to make big changes through small steps (always adapting in the process). It only works if the team embraces a learning and humble culture, in opposition to an “I’m sure” culture.
- Transparent culture: be honest with your peers and ask the management to act transparently. People fear what they don’t know.
- Get to know each other: in my experience, trust is the number one factor for a functional team. One shortcut for that is doing some get-together activities. Also, get to know yourself; ask for feedback, and make some introspection.
- Promote flat-hierarchies. Even if the company is not organized in a flat way, you can still act as all have a saying on the product and the solution. Don’t be afraid to address the stakeholders and product owners — it’s in your best interests that you normally talk to each other.
- Lean approach: do the minimum needed to have something useful; get early and frequent users’ feedback. The MVP mindset doesn’t apply only to new products; in fact, it applies mostly to new features.
- Gradual approach: reduce risk by delivering small user-driven stories; do not create long-standing branches with big changes; make frequent (and green) commits; push often. With a proper CI/CI in place, you can easily pinpoint a problematic small commit and revert it.
- Test-driven development: in my case, it provided lots of emotional safety because I knew it was hard to break things since everything had tests that were run very frequently (coupled with a CI/CD system). Also, I almost stopped using the debugger because the development was much more gradual with fewer surprises.
- Continuous delivery: Everybody has felt the release-driven anxiety. “What if I break the build?” Consider swapping from releases to continuous delivery for a constant value stream and to reduce the risks inherent to big bang (i.e. waterfall) releases.
All of these initiatives should be embraced by the whole team — and of course, the company should promote them by trusting the people and sponsoring training if required. However, if you have the drive and the passion, you can be the agent of change. One behavior can generate a chain reaction of similar behaviors. Go slowly and be positive or you’ll just be the one that complains and criticizes (which will be taken personally).
Bad environments were probably not always bad. If you see a colleague being judged, attacked, constantly interrupted, or afraid of asking things, maybe you should intervene. If everyone stays quiet about bad things, bad things can scale up.
Since we’re talking about software development, technology is surely fundamental in fear and risk management. In short, confidence in the system equals increases psychological safety as you feel safer to make mistakes. In other words, we put technology at our service to reduce or eliminate the impact of mistakes.
Jakob Nielsen proposed the 10 Usability Heuristics for User Interface Design in 1994 (later refined), which are a landmark in usability. Some of them serve as good mnemonics when adapted to the use of technology in a software team.
Visibility of system status
The design should always keep users informed about what is going on, through appropriate feedback within a reasonable amount of time.
Knowing the current system status is essential for dealing with anxiety. How can you manage a system if you don’t know its status?
A CI/CD dashboard can easily summarize the current build status. Some teams have it always displayed on a TV set in the team space.
Whenever a problem is happening in production, you also want to know what’s happening behind the scenes. For that, there’s the concept of observability, which means that one can determine the behavior of the entire system from its outputs.
Observability is a superset of monitoring. It provides not only high-level overviews of the system’s health but also highly granular insights into the implicit failure modes of the system. In addition, an observable system furnishes ample context about its inner workings, unlocking the ability to uncover deeper, systemic issues. Distributed Systems Observability
Good error messages are important, but the best designs carefully prevent problems from occurring in the first place. Either eliminate error-prone conditions, or check for them and present users with a confirmation option before they commit to the action.
If we apply this rule to software development, we’re talking about building safety nets to prevent problems in the first place. We should accept that we, as humans, can make mistakes. Automating repetitive and error-prone tasks is essential because we put the machines doing what they’re good at. Let’s go through the most common examples:
- Define an adequate testing strategy for your needs. From unit testing to end-to-end testing, all have their role in the creation of safety nets — one of the goals of automated testing.
- You should run the test suite locally often; at least before pushing code to source control. The local testing environment should be the closest possible to a realistic one (Docker can help a lot with that). Running the tests should as simple as “one step away”.
- High-feedback loop and automated build system: configure the CI/CD pipeline with care. It should trigger a build for every push, running the tests first; if a single test fails, the system is not deployed into production; if all the tests pass, you have a green build and the system is deployed. It’s all or nothing.
- Stop messing with the production database. Instead, create command-line and/or graphical tools for the support team and the developers. Besides being much safer, these guarantee you always run your domain logic when updating live data.
- Create automated scripts for frequent developer tasks (e.g.
test.sh). This reduces the likelihood of forgetting certain agreed-upon steps. Consider git hooks for some of them.
- Be careful with the broken windows theory, which applied to software development, can be stated as a “no warnings policy” and perhaps resorting to static analysis tools. If all care about the codebase, all can develop a feeling of care and ownership towards it.
In theory, you should consider automation for repetitive or error-prone tasks. Still, be aware of the cost/benefit of the dev tools you create — the cost of implementation and maintenance versus the benefit they’ll create.
Help users recognize, diagnose, and recover from errors
Error messages should be expressed in plain language (no error codes), precisely indicate the problem, and constructively suggest a solution.
If you can’t prevent the error in the first place, it should be easy to recover from it. The obvious example here is that it should be easy to revert a change that generated an issue: if something goes wrong, going back should be as simple as reverting the commit, pushing it, and waiting for another automated build (hopefully fast). Still, don’t easily fall back to this — always question how could the problem have been avoided in the first place (i.e. with some automatic mechanism in place).
As in error prevention, developers should also build tools to help them in these situations, namely CLIs and GUIs to further inspect the problem.
If some serious problem happens, talk openly about it and apply the post mortem practices. Also, keep in mind that if that happened, it's because there were no mechanisms in place to prevent it or to recover from it; don’t blame people or make them feel bad about it. Learn from the errors and act on them. Be careful not the act on the symptoms but rather find out the root cause and work on it.
The fear of the unknown is one the most common causes of fear — when you don’t know your colleagues, the codebase, the system status, the future of the product. We saw some ways to deal with that but there will always be some uncertainty because companies exist in an uncertain world. Therefore, even in a very safe environment, it’s important to accept that. Change is the only constant so I just try to make the codebase — and myself — more open to change despite following a lean-approach.
I tend to recommend that the energy you spend complaining about the team or the company should be spent trying to fix the problems in the first place — unless the problem is too big. For example, if you work in a climate of fear, it’s unlikely you can change it in the short term; some people are too resistant to change and some companies are too slow to adapt, so maybe you should consider another challenge. Fortunately, if you work in software, there are too many opportunities out there to explore. If it’s a team issue, you can ask to rotate to another team.
Life is too short (and career-life even shorter) to live stressed — and you might be developing some mental issues in the background. Why do mental issues get so underrated when compared with physical harm? Don’t be ashamed if you need professional help. Take care of yourself.