“No url found for submodule path” errors can be tricky to track down; if you think you have all your submodules set up correctly. I ran into this very issue today! I cloned one of my team’s repositories and could not then pull the submodules due to this error. After some searching, I was able to find a simple solution to resolve the error from incorrectly adding submodules to your repository.
$ git submodule update --init --recursive
fatal: No url found for submodule path 'common_source'in .gitmodulesCode language:JavaScript(javascript)
I was left scratching my head because I did in fact have my .gitmodules set correctly. I should not have been seeing this “No url found for submodule path” error.
Somehow, I’m not sure how, but somehow the submodule got added at the wrong path initially and then was not correctly removed. Using the git ls-files command, I was able to see that the submodule was indeed listed twice. That was what was causing the error.
As developers, we are constantly striving to write clean, maintainable code. But as codebases grow and evolve, it can be challenging to ensure consistency across the entire project. Consistent code formatting can make it easier to read and understand code, and can even reduce bugs and errors.
To address this issue, many teams use code formatters like clang-format to automatically enforce a consistent style across their codebase. But how can we ensure that our code is properly formatted before it even reaches the code review stage? This is where Bitbucket Pipelines comes in.
By integrating clang-format checks into your Bitbucket Pipeline, you can automatically test that your code is properly formatted on every push or pull request. This helps catch formatting errors early in the review process, making it easier to maintain a consistent codebase and ultimately reducing technical debt.
In this blog post, we’ll walk through how to set up a Bitbucket Pipeline step to test code formatting with clang-format. We’ll also discuss best practices for code formatting and how to integrate these checks into your development workflow. So, let’s get started!
Add a consistency check step to your pipeline
To test that formatting is correct with clang-format in a Bitbucket Pipeline, you can add a step to install clang-format and then run a check against your code files. Here’s an example of how you could do this:
Installs clang-format by running apt-get update and apt-get install -y clang-format.
Prints the version of clang-format by running clang-format --version. This is useful for debugging and logging information in your pipeline.
Runs a find command to search the repository for all C/C++ source files (extensions *.h, *.c, *.hpp, and *.cpp). If you have code in files with other extensions, you can add them to the search. Just add -o -name '*.<your extension>' after the '*.cpp' search term. Mind the space between the last term and the closing ')'!
Runs clang-format against the found code files with the -style=file flag, which tells clang-format to use the formatting style specified in the .clang-format file in the root of your repository. The command generates an XML report of the formatting changes.
Pipes the XML report to grep "<replacement ", which searches for any lines in the report that contain the <replacement> tag. This tag indicates that clang-format made a formatting change to the code. If any replacements are found, the pipeline will exit with an error code (exit 1). Otherwise, it will exit successfully (exit 0).
The pipeline step will pass if this returns with exit code of 0 and fail otherwise. Place this step as the first step in your pull request pipeline and when a pull request is opened it will fail quickly if there are formatting issues. This indicates to the developer they need to fix those issues before continuing.
Note that this checks the entire repository, not just the changes!
Best Formatting Practices
Ensuring consistent formatting is definitely a good practice to work into your development practice. To make it easy for developers to do this, many IDEs support formatting files per some standard. For example, Eclipse allows you to define a style and easily format highlighted sections or the entire file. VS Code has options to format your code when you save a file, in addition to formatting specific sections of code you are working on.
Formatting options in VS Code.
Outside of your IDE, my favorite option is to use a git hook to format your patches as you commit them to the repository. By using the pre-commit hook and some clang-format scripts, you can ensure that any new code or changed code gets formatted properly before getting pushed to the server. Read more about how to set that up in my other post.
Best practices for enforcing consistent formatting comes down to two approaches, in my opinion, and depends on whether you are working with an established code base or a new one.
Consistent Formatting for Established Code Base
For established code bases, it may not be desirable or feasible to format all the code. That would require a huge amount of retest and revalidation that just may not be possible. In that case, set up your pipeline to just check the formatting on patches in the pull request. As developers make changes to fix bugs or add features, those new changes will go through the testing and validation process and also get the correct formatting applied. Over time, the consistency of the formatting will increase as more and more of the code gets updated.
To help developers automate this, set up the development environment to format changes with a git pre-commit hook. This will cut down on pipeline failures which enforce that patches are formatted properly on pull requests.
An example pipeline that enforces formatting on just patches is shown here:
definitions:
steps:
- step: &Check-Patch-Formatting
name: Check code formatting with clang-format-diff
image: atlassian/default-image:4
script:
# Install clang-format-diff
- apt-get update && apt-get install -y clang-format
# Run clang-format-diff on modified files
- if [[ $(git diff -U0 --no-color --relative origin/master...origin/${BITBUCKET_BRANCH} | clang-format-diff -p1) ]]; then exit1; fi
pipelines:
pull-requests:
'**':
- step: *Check-Patch-FormattingCode language:PHP(php)
Consistent Formatting for New Code Base
When working with a new project and a new code base, there is no reason why you can’t enforce formatting right off the bat.
First, set up your IDE to format your files on save, to ensure they stay properly formatted. In addition, ensure you have the pre-commit hook installed to validate formatting (and to fix it) before committing. Finally, set up the pipeline with two steps: one for validating the patch on each PR, and another that checks the entire code base as part of a nightly (or weekly) build process. The first pipeline in this post shows this type of pipeline step (for a pull-request).
Conclusion
In conclusion, maintaining consistent code formatting is crucial for writing readable, maintainable code. With Bitbucket Pipelines and clang-format, we can automate the process of checking our code formatting and catch errors before they make it into production. By setting up a simple Pipeline step to run clang-format checks, we can ensure that our codebase stays tidy and easy to read.
Remember that consistent code formatting is just one piece of the puzzle when it comes to writing high-quality code. It’s important to also focus on good design principles, writing clear and concise code, and properly testing our applications. By making conscious decisions and striving to improve our development practices, we can create software that is not only functional, but also maintainable and sustainable in the long run.
Additional Resources
Bitbucket Pipelines documentation: The official documentation provides a detailed guide on setting up and using Pipelines, including how to integrate with external tools like clang-format.
clang-format documentation: The official documentation provides a comprehensive guide on using clang-format to format code, including style options and command line usage.
GitHub Action for clang-format: If you use GitHub instead of Bitbucket, you can check out this GitHub Action which can help you integrate clang-format checks into your workflow.
In today’s fast-paced and ever-changing world of software development, it’s more important than ever to be a conscious developer. Being a conscious developer means being aware of the impact your work has on others, constantly improving your skills and processes, and working collaboratively with your team to achieve shared goals. In essence, a conscious developer is aware that the choices they make in their code have an impact beyond just the application.
One way to become a more conscious developer is by applying agile principles in your day-to-day work. Agile principles emphasize collaboration, adaptability, and continuous improvement, all of which can help you become more aware of your choices and their effects, ultimately improving the quality of your work.
In this blog post, we’ll explore how agile principles can help you become a more conscious software developer and provide practical examples of how you can incorporate these principles into your personal development process. Whether you’re a seasoned developer or just starting out, this post will provide valuable insights into how you can become a more effective and conscious member of your development team.
Applying Agile: Collaboration
Collaboration is a fundamental aspect of agile principles. In an agile team, everyone works together towards a shared goal, communicating regularly to ensure that everyone remains aligned on the goal. By working collaboratively, team members can identify potential issues and blockers early on and take appropriate action quickly to realize rapid progress. This emphasis on collaboration can make you more conscious of your role as a software developer.
When working collaboratively, you become more aware of how your work impacts others. You gain an understanding of the dependencies between different parts of your codebase and how changes to one area can affect a team member. This awareness can help you make more informed decisions as you work and ensure that you’re not introducing unnecessary bugs or breaking other parts of the system.
Furthermore, collaboration can help you become more empathetic towards your colleagues. By working and communicating closely with them, you gain an appreciation for the challenges and constraints that they face. This understanding can help you be more considerate of other team members’ needs and concerns, leading to better collaboration and more effective outcomes.
Overall, collaboration is a powerful tool for becoming a more conscious software developer. By engaging closely with your team, you can become more aware of your impact on others and develop a more empathetic and effective approach to your work.
Applying Agile: Adaptability
Another key agile principle is adaptability. In an agile environment, change is expected and embraced. Rather than following a rigid plan, teams continuously assess their progress and adjust their approach as needed to achieve their goals. Applying agile principles like adaptability can help you become more conscious of your own limitations and help you improve your personal process.
By acknowledging your own limitations, you can become more receptive to feedback and open to learning from others. This willingness to learn can help you become a more effective developer and improve the quality of your work. Additionally, being adaptable can help you identify areas where you can improve your development process.
For example, at one point in my career, I was juggling about six different projects at once. Things quickly got really hard to manage with all the different requirements and tasks assigned to me. It was at that point that I took a step back. I analyzed my situation and realized that I could do better. I decided to manage my own backlog by running a mini-sprint each day.
So, I began to hold a mini-sprint planning meeting with myself each morning. Here I would analyze my backlog and tackle the most important items on it. I then laser focus my attention on that small set of tasks for the day. At the end of the day, I’d hold a mini-retrospective with myself and prepare my backlog for the next day.
It was amazing to me how this seemingly simple action changed my productivity. I was able to excel in all of those projects, producing high-quality work on time and on schedule. I’ve been executing mini-sprints for myself ever since!
In summary, adaptability is a key component of becoming a conscious software developer. By embracing change and being open to learning, you can overcome your limitations and identify areas for improvement.
Applying Agile: Continuous Improvement
Continuous improvement is another essential aspect of the conscious software developer. Rather than viewing development as a one-time effort, teams in an agile environment strive to continuously refine and improve their work. This focus on continuous improvement can help you become more conscious of your own growth as a developer.
By constantly seeking to improve your skills and processes, you can become more effective at your job and deliver higher-quality software. This may involve learning new programming languages or frameworks, experimenting with new development techniques (such as design patterns!), or simply seeking feedback from your colleagues. By seeking out new opportunities for growth, you can stay up-to-date with the latest industry trends and ensure that your skills remain relevant.
Furthermore, continuous improvement can help you identify areas where you can improve your development process. By regularly assessing your work and soliciting feedback from your colleagues, you can identify areas where you may be able to streamline your workflow or improve your code quality. Collaboration and continuous improvement go hand in hand. This focus on continuous improvement can help you create more robust, efficient, and effective software that meets the needs of your users.
Honestly, continuous improvement is probably the most critical component of becoming a conscious software developer. By constantly seeking to improve your skills and processes, you can become more effective at your job, deliver higher-quality software, and ensure that your skills remain relevant in an ever-changing industry.
Conclusion
In conclusion, agile principles can be a powerful tool for becoming a more conscious software developer. By emphasizing collaboration, adaptability, and continuous improvement, agile principles can help you become more aware of your impact on others, identify areas for improvement, and create more efficient and effective software. Whether you’re a seasoned developer or just starting out, applying agile principles into your personal development process can help you become a more effective and conscious member of your team. So why not give it a try and see how it can improve your development workflow and outcomes?
Additional Resources
Agile Manifesto: The official website of the Agile Manifesto provides an overview of agile principles and values. You can apply each of these to your own day-to-day life as a developer.
Scrum.org: Scrum is a popular agile framework that is used in many business settings. Scrum.org provides free resources such as webinars, case studies, and articles about applying Scrum in different contexts. It can provide valuable insight into applying agile principles to your daily routine. Read more on their blog and resources pages.
Agile Alliance: A non-profit organization that provides resources for people who want to learn about agile principles and practices. The website includes articles, webinars, podcasts, and other resources that can help you apply agile principles to your personal development. Pay close attention to the Agile Essentials portion of their site.
The Lean Startup: The Lean Startup methodology is based on agile principles and can be applied to personal development. The website provides free resources such as articles and videos about how to apply lean principles in your work.
Leading Agile: LeadingAgile.com is a consulting and training company focused on helping organizations adopt agile principles and practices. They provide a range of services including coaching, training, and transformation services for enterprise-level agile adoption. They offer a Personal Agility Canvas that can help you visualize your personal goals and priorities and how you add value to your organization.
Managing stress and avoiding burnout is essential for maintaining optimal productivity and mental health. When left unchecked, stress can lead to burnout, a state of emotional, physical, and mental exhaustion. Burnout can result in reduced productivity, poor job performance, and increased absenteeism, and can even lead to serious health problems. In the fast-paced and demanding field of software development, managing stress and avoiding burnout is particularly important. By adopting a conscious mindset as a developer and practicing stress management techniques to prevent burnout, software developers can maintain their well-being, sustain high performance, and enjoy a more fulfilling career.
In this blog post, we will explore some practical tips for managing stress and burnout as a conscious software developer. By incorporating these tips into your daily routine, you can boost your productivity, improve your mental health, and enjoy a more fulfilling career. So, let’s dive in and learn how to manage stress and burnout as a conscious software developer.
Causes of Stress and Burnout
Stress and burnout are prevalent in the software development industry, and it’s crucial to understand their causes to avoid them.
Common causes of stress and burnout in software development include:
High-pressure work environment: Deadlines, workload, and client expectations can put immense pressure on developers.
Lack of work-life balance: Long hours at work, working weekends, and a constant work-focus can disrupt the balance between work and personal life.
Ineffective communication: Poor communication between team members, managers, and clients can result in misunderstandings and stress.
Challenging technical requirements: While in most cases this is expected, developing complex software that meets stringent technical requirements can be challenging and time-consuming, leading to frustration and burnout.
It’s essential to identify the root causes of stress and burnout to prevent them from taking a toll on your mental and physical health. By recognizing the causes, you can take proactive measures to reduce stress and improve your mental health. In the next section, we will explore practical tips for managing stress and avoiding burnout as a conscious software developer.
Managing Stress and Burnout
In many cases, you will not be able to completely remove the cause of the stress. However, in all cases I have dealt with in my career, simply being aware of the source is 99% of the battle.
By being aware that the communication in your team culture is lacking, you know you can work to improve your own communication and then communicate that to your manager and team lead.
By understanding that your clients and customers like to continually expand and modify the project scope and complexity can help you know that you need to plan early and manage your time effectively to succeed.
If you are a workaholic, then you’ll understand that you need to plan time to step away and recharge at times.
Here are a few practical tips that have worked well for me to counter stress and burnout that you can put into action today.
Connecting with nature: When we spend time in nature, we receive unexpected benefits that go beyond what we set out to achieve. By simply being in the presence of nature and taking a walk, we open ourselves up to a wealth of sensory experiences that can be restorative and rejuvenating.
Incorporating regular exercise: Exercise is an effective stress-reliever that boosts mental health and improves physical well-being.
Eating healthy: A well-balanced diet provides the necessary nutrients to improve overall health and reduce stress levels. In addition, when we are healthy and fit, we have more capacity to experience joy, which in turn helps us to produce high-quality work.
Maintaining a proper sleep schedule: A good night’s sleep is essential for optimal physical and mental health. When you are sleepy or just running on caffeine, your productivity plummets and the quality of your work suffers.
Set realistic goals and deadlines
Unrealistic goals and deadlines can lead to burnout and a decrease in productivity. As a conscious software developer, it’s essential to set realistic goals and deadlines that align with your capabilities and workload. This can help you avoid burnout and stay motivated. Here are some tips for setting realistic goals and deadlines:
Break down big projects into smaller tasks.
Prioritize tasks based on importance and urgency.
Set achievable deadlines for each task.
Note that this does not mean that you should not set lofty and hard to reach goals. I love how Sandy Gallagher breaks down goals into the ABCs. When setting your own personal goals, stick to the Type C goals — dream big and stretch yourself! Get uncomfortable enough that you have to reach outside your current self to realize your dream.
But sometimes you work with clients who are changing scope or reducing schedule. So be sure to communicate your task breakdown and deadlines clearly, so they understand the impact of their decisions. I have yet to meet a client or stakeholder who is so unreasonable that they push their agenda anyway when presented with clear repercussions. In all situations I have been in, we were able to work together to manage the scope and come to a solution where everyone was satisfied.
Learn to manage time effectively
I recently wrote a couple of articles on specific techniques and strategies for effectively managing your time. Time management is crucial for preventing stress and burnout. As a conscious software developer, it’s important to:
Identify time-wasters: Identify activities that waste your time, and reduce or eliminate them.
Prioritize tasks: Focus on tasks that are essential and urgent. Make use of an Eisenhower Matrix or the 80/20 rule to help you prioritize all your tasks.
Use productivity tools: Use tools that can help you manage your time, such as time-tracking software and to-do lists. Most companies I have dealt with use issue trackers to track tickets for developer work. Most of these will have a board where you can keep track of the progress of each of your tasks.
One other technique I have found really useful is a mini-sprint. Simply plan out your tasks for each day as you would in a team sprint. Use your task list as a guide and allocate the time in each mini-sprint accordingly. During each mini-sprint, give your full attention to the tasks at hand, avoiding the temptation to work on other things.
Take breaks and practice mindfulness
Taking breaks and practicing mindfulness can help you manage stress and improve your mental health. Here are some tips:
Take short breaks throughout the day to stretch, walk, or do breathing exercises. A Pomodoro timer can help remind you when to take a break and for how long. In my experience, simply stepping away from a complex problem for a while and allowing my mind to rest, led me directly to a solution. Taking a break from work is not a waste of time or a sign of a lazy employee. Rather, it is a strong sign of someone committed to the excellence of their craft so much that they know how to always perform at their peak.
Practice mindfulness techniques such as meditation or deep breathing to calm your mind and reduce stress. The goal of mindfulness is to generate awareness. By generating awareness, we strengthen our will to make wise use of the space between a stimulus, our feelings, and our response. Which helps us manage and deal with stressors. Which helps us avoid burnout. Which helps us be more satisfied with our career and more capable of experiencing joy.
Conclusion
In conclusion, managing stress and avoiding burnout is crucial for software developers to maintain optimal productivity and mental health. As a conscious software developer, prioritizing self-care, setting realistic goals and deadlines, managing time effectively, and taking breaks and practicing mindfulness can help prevent stress and avoid burnout.
By incorporating these tips into your daily routine, you can improve your work-life balance, reduce stress levels, and enjoy a more fulfilling career as a software developer. So, let’s prioritize self-care and practice these tips to become a conscious software developer who can handle stress and avoid burnout.
Additional Resources for Managing Stress and Burnout
The ABCs of Goal Setting: Sandy Gallagher talks about the importance of setting goals and provides a simple three-step process to set achievable goals: 1) Align your goals with your values, 2) Be specific about what you want to achieve, and 3) Create a plan of action. The article emphasizes the need to have a clear and compelling reason for setting a goal and the importance of taking action towards achieving it.
How to Manage Stress with Mindfulness and Meditation: The article explains how mindfulness and meditation can help reduce stress by increasing self-awareness, improving focus, and promoting relaxation. The article also provides practical tips on how to practice mindfulness and meditation to manage stress, including breathing exercises and body scans.
Nurturing the 4 Pillars of Well-Being for a Fulfilling Fatherhood: While focused on fatherhood, this article identifies four key pillars of well-being: physical, mental, emotional, and spiritual. The article provides tips and strategies for nurturing each of these pillars, including regular exercise, stress management techniques, and connecting with others. The article emphasizes the importance of taking care of oneself in order to be the best possible father, which also apply to being a conscious software developer.
Software development is not just about writing code and designing algorithms. It is a team effort that involves constant communication and collaboration between team members. Communication skills are therefore an essential part of a software developer’s toolkit — especially for conscious developers. Effective communication can help teams work together smoothly, solve problems more efficiently, and ultimately deliver high-quality software products. In contrast, poor communication can lead to project delays, misunderstandings, and even failure.
In this blog post, we will explore the vital role of communication skills in software development. We will delve into the different types of communication skills, the negative impact of poor communication, and how to improve how we communicate. We will also discuss how effective communication can enhance team collaboration and how it is crucial in agile methodologies.
If you are a conscious software developer looking to improve your skills, then this post is for you. We will provide you with insights and tips on how to become a better communicator and a more effective member of your team. So, let’s dive in!
What are communication skills?
Communication skills refer to the ability to exchange information, ideas, and thoughts effectively. It includes not only verbal communication but also non-verbal communication such as body language, facial expressions, and tone of voice. As a conscious developer, effective communication is critical because you must convey technical information to not only your team, but various stakeholders such as project managers and clients as well.
This is a skill that provides the basis for all communication. In my honest opinion, the best listeners are the absolute best communicators. The ability to actively listen and understand what others are saying is an essential skill that helps in comprehending the requirements of the project, identifying the problems, and building a rapport with team members.
Speaking
Speaking begins with listening because you cannot convey information clearly, concisely, and confidently if you don’t have an understanding of your audience or what your requirements are. This skill is essential when communicating with stakeholders, presenting technical information, and giving status updates.
Writing
Effective writers are also effective listeners. Writing is the ability to express ideas and information in written form. It includes writing emails, documenting code, creating user manuals, and other written materials. While writing may not be the most exciting part of good software development, it remains an essential one.
Relating
Relatability is a natural byproduct of being a strong listener. It is the ability to interact and build relationships with others effectively. This includes skills such as empathy, emotional intelligence, and teamwork which are vital for creating a positive and productive work environment.
What is the impact of poor communication?
Poor communication skills can have a significant impact on software development projects and teams. It can lead to misunderstandings, delays, and project failures, as well as isolated, ineffective people. Here are some examples of how poor communication can have a negative impact:
Misunderstandings: Misunderstandings can occur when team members do not communicate effectively. For example, a developer may misunderstand the requirements of a project, leading to incorrect assumptions and time spent coding the wrong solution. This results in project delays and rework.
Delays: When team members are not aware of the status of the project or the tasks assigned to them, delays are the natural result. This can lead to confusion and duplication of effort, as well as frustration from customers and other stakeholders.
Conflict: Poor communication can lead to conflict among team members. For example, if a team member feels that their ideas or concerns are not being heard, they may become resentful, leading to a negative and toxic work environment.
Project Failure: Poor communication can ultimately lead to project failure. If your team is not communicating effectively, it can result in a product that does not meet the requirements or expectations of the client.
In summary, poor communication can lead to misunderstandings, delays, conflict, and ultimately project failure. It is therefore essential for conscious developers to strengthen and improve their communication skills.
How can communication skills be improved?
Effective communication is a skill that can be improved with practice and training. It comes down to becoming aware of where you fall short, so you can focus your effort and learning there.
This most essential communication skill involves listening attentively and asking questions to clarify understanding. To improve this skill, focus on being present in the moment, maintaining eye contact, and avoiding distractions. You can truly listen to just one voice at a time. So put your phone down when talking with a colleague. Listen to your direct report completely before thinking about your own response. Give your full attention to the meeting your presence was requested to be in.
Regular Communication and Feedback
Regular communication and feedback can help in improving communication skills because it helps develop the necessary awareness to take action. Strive to communicate regularly with your team members and project stakeholders, providing updates, and seeking feedback to ensure everyone is on the same page.
Training and Development Opportunities
Take advantage of training and development opportunities to improve your communication skills. This can include attending communication workshops, reading books on effective communication, and seeking mentorship from experienced developers. There are a few additional resources at the end of this post to help you get started.
Empathy and Understanding
Empathy and understanding are crucial for effective communication. Strive to understand the perspective of others, considering their needs and expectations. This can help in building better relationships with team members and project stakeholders.
By empathizing with team members, you can better understand their needs, motivations, and challenges. This understanding will help you work more effectively with your team members, creating a more collaborative and supportive work environment.
Empathy will also help you understand the needs and perspectives of stakeholders and customers. This helps you create software products that meet the needs of your users and deliver more value to the business.
By improving communication skills, you can ensure that everyone is on the same page, avoid misunderstandings, and ultimately deliver high-quality software products. But more importantly than even that, you build strong relationships in your team. And strong teams are successful teams.
How do communication skills work in team collaboration?
Effective communication skills are essential for team collaboration in software development. When a team exhibits good communication skills, all communication channels are open and considered safe for people to use. Here are some ways communication skills impact team collaboration:
Building Trust: Effective communication skills help build trust among team members, which is crucial for successful collaboration. Trust allows team members to work together, share ideas, and make decisions collectively.
Resolving Conflicts: Conflicts are inevitable in any team. Effective communication skills help resolve conflicts by ensuring that team members can express their concerns and work towards a mutually beneficial solution.
Sharing Knowledge: Effective communication skills allow team members to share knowledge, ideas, and expertise. This sharing of knowledge can lead to better decision-making and ultimately result in higher quality software products.
Building Rapport: Effective communication skills allow team members to build rapport, which is crucial for creating a positive and productive work environment. A positive work environment leads to higher job satisfaction, which results in better performance and ultimately higher quality software products.
Effective communication skills are crucial for team collaboration in software development. With a constructive communication culture, team members feel safe to share their thoughts, successes, ideas, and concerns. This enables the team to establish trust, resolve conflicts, share knowledge, and build rapport, ultimately resulting in an extremely high-performant team.
Conclusion
In conclusion, effective communication skills are crucial for software developers to become conscious developers who can build high-quality software products. These skills are essential for team collaboration, agile methodologies, and project success. Software developers should strive to improve their communication skills by actively listening, seeking regular feedback, taking advantage of training and development opportunities, and building empathy and understanding. Open communication channels are critical for team collaboration, trust building, conflict resolution, better performance, and job satisfaction. By becoming conscious developers who prioritize effective communication, software developers can ensure the success of their projects and contribute more value to their team.
I challenge you to start today to begin to improve your communication skills. Start by making a conscious effort to become aware of where you fall short; then make a plan to learn and grow!
Additional Resources
EQ Applied: Justin provides some great resources here to improve your overall emotional intelligence, or EQ. His website focuses on EQ and how it can be applied to work and life. The site provides resources, articles, and courses to help individuals develop their emotional intelligence, improve their communication skills, and build better relationships.
Three Key Communication Skills That Software Developers Must Master: This article by Kailyn Nelson highlights three key communication skills that software developers should cultivate: active listening, empathy, and clear articulation. It explains how these skills can help developers build better relationships with team members, stakeholders, and customers, leading to more successful projects and better software products.
5 Ways to Improve Communication Skills for Software Developers: This article by Borys Nikolenko outlines five essential ways for software developers to improve their communication skills, including active listening, asking questions, practicing empathy, using appropriate language, and seeking feedback. The article emphasizes the importance of effective communication skills for successful software development and provides practical tips for developers to implement in their work.
A lot has been written on design patterns, but they were woefully absent in my education as a developer. I learned about them after a few years as a developer, and they were game-changing for me. In this post, I want to share with you a few of the most useful design patterns for C/C++ that I often use in my own designs.
When I first learned about design patterns, they were presented to me as the proper way to organize classes to solve specific types of problems. In fact, the first design pattern I was introduced to was the state pattern for implementing state machines. Up until that point, I was resorting to complex case statements with lots of conditional checks to implement my state machine logic, simply because that was all I knew.
I was taught that I could encapsulate all the transition logic into state classes. I was blown away by how much that single refactor could simplify my code, and how much easier it was to add and remove states.
Over time, I started to find that the state pattern applied to so many other problems, and then that got me into trouble. I had this shiny new tool in my toolbox and I just wanted to use it for everything!
When Should Patterns Be Applied?
Thanks to some great mentors, I was able to step back and start looking at other patterns that existed — the factory pattern, adapters, singletons, and strategies. All of these different tools could be used in my designs to solve different problems.
And that is the key — design patterns are tools, nothing more. As with any tool in your toolbox, there is a time and place to use it. Design patterns are no different. For C/C++ developers, they can lead to some strikingly simple and elegant solutions, but sometimes they can be a rabbit hole that will just add unnecessary abstraction to your code when a simple, straightforward solution would do.
As conscious developers, our goal is to design amazing applications, using the best tools available to us. We understand that if a certain tool does not work for a given problem, we move on. We don’t try to force a tool on a design, which is one of the major pitfalls for new developers once they learn about design patterns.
Types of Design Patterns
The literature on design patterns typically breaks all the common patterns down into three primary categories, each of which deal with a different aspect of the development process:
Creational Patterns
Structural Patterns
Behavioral Patterns
I’m going to share with you a few of my favorite design patterns from each category. You can find tons of information about design patterns through a simple search, however one resource I have found extremely valuable is refactoring.guru. That site has lots of good information, all of which can be applied to being a conscious developer!
Creational Design Patterns
Creational design patterns are used in software development to provide a way to create objects in a flexible and reusable way. They help to encapsulate the creation process of an object, decoupling it from the rest of the system. This makes it easier to change the way objects are created or to switch between different implementations.
Creational design patterns provide a variety of techniques for object creation, such as abstracting the creation process into a separate class, using a factory method to create objects, or using a prototype to create new instances. These patterns are useful when the creation of objects involves complex logic or when the object creation process needs to be controlled by the system.
Some examples of creational design patterns include the Singleton pattern, which ensures that only one instance of a class is created, and the Factory pattern, which provides a way to create objects without specifying the exact class of object that will be created.
Overall, creational design patterns help to improve the flexibility and reusability of software systems by providing a more structured and standardized approach to object creation.
Singleton Creational Pattern
The Singleton design pattern is a creational pattern that ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful in situations where only one instance of a class should exist in the program, such as managing system resources or ensuring thread safety. However, it should be used with caution, as it can introduce global state and make testing more difficult.
UML describing the Singleton Creational Pattern
In C++, this can be achieved by doing two things:
Make the constructor for your class private.
Provide a static method to access the singleton instance.
The static method creates the singleton instance the first time it is called and returns it on subsequent calls. Ultimately, this static method typically returns either a reference to the newly created class, or a pointer to it. I prefer to use references because they are safer, but there have been cases where I create a std::shared_ptr in my static function and return that to the caller to access the singleton.
C++ Example of the Singleton Creational Pattern
To implement the Singleton pattern in C++, a common approach is to define a static method in the class definition. Then, in the implementation file, define the static method and have it define a static member variable of the class type. The static method will initialize the singleton instance if it has not been created yet, and return it otherwise. Here’s an example:
classSingleton {public:
static Singleton& getInstance(){
static Singleton instance; // The singleton instance is created on the first call to getInstance()return instance;
}
private:
Singleton() {} // The constructor is private to prevent typical instantiation Singleton(const Singleton&) = delete; // Delete the copy constructor to prevent copying Singleton& operator=(const Singleton&) = delete; // Delete the assignment operator to prevent copying also};
Code language:C++(cpp)
In this example, the getInstance() method returns a reference to the Singleton instance, creating it on the first call using a static variable. The constructor is private to prevent instantiation from outside the class. The copy constructor and assignment operator are deleted to prevent copying.
Your getInstance() method can take arguments as well and pass them along to the constructor, if that is desired. You need to be aware that those are only used to create the object the first time, and never again. For this reason, I consider it to be best practice for getInstance() to not take any arguments to not confuse a user making a call.
Abstract Factory Creational Pattern
The Abstract Factory design pattern is a creational pattern that provides an interface for creating families of related objects without specifying their concrete classes. This pattern is useful in situations where there are multiple families of related objects, and the actual type of object to be created should be determined at runtime. For example, in a messaging application, there might be multiple types of messages that need to be created. Each type of message should be consistent in interface, but with specific behavior.
UML describing a modified Abstract Factory Creational Pattern
C++ Example of the Abstract Factory Creational Pattern
In C++, this can be achieved by defining an abstract base class for each family of related objects. Then define concrete subclasses for each type of object in each family. Here’s an example from a recent project of mine:
structEvent {std::string m_uuid; // define a UUID for the eventstd::string m_msg; // message related to the eventstd::chrono::time_point m_timestamp; // time event occurredvirtual ~Event () {}
virtualvoidhandle()= 0;
virtualvoidclear()= 0;
};
structErrorEvent :public Event {
uint32_t m_id; // Error ID for the specific error that occurredvoidhandle()override{
// Perform "handling" action specific to ErrorEvent }
voidclear()override{
// Perform "clear" action specific to ErrorEvent }
};
structStateChange :public Event {
uint32_t m_state; // state identifiervoidhandle()override{
// Perform "handling" action specific to StateChange }
voidclear()override{
// Perform "clear" action specific to StateChange }
};
classComponent {public:
template <
classT,std::enable_if_t<std::is_base_of_v<Event, T>, bool> = true>
std::shared_ptr<T> GetEvent(void){returnstd::make_shared<T>(); }};Code language:C++(cpp)
In this example, the Event base class defines virtual methods for handling the event and clearing the event. The ErrotEvent and StateChange classes are concrete subclasses that implement these methods for the specific events. The Component class defines a GetEvent() methods for creating Events. Now, when I want to create a new event, I just inherit the Event base class and can call GetEvent() to create a new instance of the event.
Pay special attention to lines 37. This ensures that the compiler will give an error if I try to call GetEvent with a data type that is not derived from my Event class. And that is ideal — turning what were once run time errors into compiler errors.
This is just one use of the Abstract Factory pattern. There are others that allow you to define specific behavior for different platforms or architectures as well!
Structural Design Patterns
Structural design patterns are used in software development to solve problems related to object composition and structure. These patterns help to simplify the design of a software system by defining how objects are connected to each other and how they interact.
Structural design patterns provide a variety of techniques for object composition, such as using inheritance, aggregation, or composition to create complex objects. They also provide ways to add new functionality to existing objects without modifying their structure.
Some examples of structural design patterns include the Adapter pattern, which allows incompatible objects to work together by providing a common interface, and the Decorator pattern, which adds new behavior to an object by wrapping it with another object.
Overall, structural design patterns help to improve the modularity, extensibility, and maintainability of software systems by providing a more flexible and adaptable way to compose objects and structures. They are particularly useful in large and complex software systems where managing the relationships between objects can become challenging.
Adapter Structural Pattern
The Adapter design pattern is a structural pattern that allows incompatible interfaces to work together. In C++, this pattern is used when a class’s interface doesn’t match the interface that a client is expecting. An adapter class is then used to adapt between the two interfaces.
UML describing the Adapter Structural Pattern
C++ Example of the Adapter Structural Pattern
To use this in C++, define a class that implements the interface that the client expects, and internally use an instance of the incompatible class that needs to be adapted. Here’s an example:
In this example, the ExpectedInterface class defines the interface that the client expects, which is the request() method. The IncompatibleInterface class has a method called specificRequest() that is not compatible with the ExpectedInterface. The Adapter class implements the ExpectedInterfaceand internally uses an instance of the IncompatibleInterface class to make the specificRequest() method compatible with the ExpectedInterface.
Using the Adapter pattern allows us to reuse existing code that doesn’t match the interface that the client expects, without having to modify that code. Instead, we can write an adapter class that mediates between the incompatible interface and the client’s expected interface.
Decorator Structural Pattern
The Decorator design pattern is a structural pattern that allows adding behavior or functionality to an object, without affecting other objects of the same class. This pattern is commonly used to attach additional responsibilities to an object by wrapping it with a decorator object.
UML describing the Decorator Structural Pattern
C++ Example of the Decorator Structural Pattern
To use this pattern in C++, define an abstract base class that defines the interface for both the component and the decorator classes. Then define a concrete component class that implements the base interface and a decorator class that also implements the same interface and holds a pointer to the component object it is decorating. Here’s an example:
classComponent {public:
virtual ~Component() {}
virtualvoidoperation()= 0;
};
classConcreteComponent :public Component {
public:
voidoperation()override{
// Perform some operation }
};
classDecorator :public Component {
public:
Decorator(Component* component) : component_(component) {}
voidoperation()override{
component_->operation();
}
private:
Component* component_;
};
classConcreteDecoratorA :public Decorator {
public:
ConcreteDecoratorA(Component* component) : Decorator(component) {}
voidoperation()override{
Decorator::operation();
// Add some additional operation }
};
classConcreteDecoratorB :public Decorator {
public:
ConcreteDecoratorB(Component* component) : Decorator(component) {}
voidoperation()override{
// Do not call base class operation() to remove that functionality from this instance// Add some different additional operation }
};
Code language:C++(cpp)
In this example, the Component class defines the interface that the concrete component and decorator classes will implement. The ConcreteComponent class is a concrete implementation of the Component interface. The Decorator class is an abstract class that also implements the Component interface and holds a pointer to a component object it is decorating. The ConcreteDecoratorA and ConcreteDecoratorB classes are concrete decorators that add additional behavior to the ConcreteComponent object by calling the Decorator base class’s operation() method and adding their own additional behavior.
Note that if you only needed a single decorator (i.e., you did not require decorators A and B), you can get away with simply adding the necessary additional operations directly to the Decorator class in the example. However, following the pattern as shown requires not much more work immediately and will make it easier when you need to define additional concrete decorators down the road.
Using the Decorator pattern allows us to add or remove behavior from an object without affecting other objects of the same class. It also allows us to use composition instead of inheritance to extend the functionality of an object.
Facade Structural Pattern
The Facade design pattern is a structural pattern that provides a simplified interface to a complex subsystem of classes, making it easier to use and understand. A Facade class can then be used by clients to access the subsystem without having to know about the complexity of the lower-level classes.
UML describing the Facade Structural Pattern
C++ Example of the Facade Structural Pattern
In C++, this pattern can be used to create a high-level interface that hides the complexity of the lower-level subsystem. To implement this pattern in C++, we can define a Facade class that provides a simplified interface to the subsystem classes. Here’s an example:
In this example, the SubsystemA and SubsystemB classes represent the complex subsystem that the Facade class will simplify. The Facade class provides a simplified interface to the subsystem by hiding the complexity of the lower-level classes. The Facade class also holds instances of the subsystem classes and calls their methods to perform the operation.
This pattern allows us to simplify the interface to a complex subsystem, making it easier to use and understand. But, my favorite use of it is to isolate clients from changes to the subsystems by abstraction.
Behavioral Design Patterns
Behavioral design patterns are used in software development to address problems related to object communication and behavior. These patterns provide solutions for managing the interactions between objects and for coordinating their behavior.
Behavioral design patterns provide a variety of techniques for object communication, such as using message passing, delegation, or collaboration to manage the interactions between objects. They also provide ways to manage the behavior of objects by defining how they respond to events or changes in the system.
Some examples of behavioral design patterns include the Observer pattern, which allows objects to be notified when a change occurs in another object, and the Command pattern, which encapsulates a request as an object, allowing it to be parameterized and queued.
Overall, behavioral design patterns help to improve the flexibility, modularity, and extensibility of software systems by providing a more structured and standardized way to manage object communication and behavior. They are particularly useful in systems that involve complex interactions between objects, such as user interfaces, network protocols, or event-driven systems.
Mediator Behavioral Pattern
The Mediator design pattern is a behavioral pattern that promotes loose coupling between objects by encapsulating their communication through a mediator object. In C++, this pattern can be used to reduce dependencies between objects that communicate with each other.
UML describing the Mediator Behavioral Pattern
C++ Example of the Mediator Behavioral Pattern
To implement the Mediator pattern in C++, we can define a Mediator class that knows about all the objects that need to communicate with each other. The Mediator class then provides a centralized interface for these objects to communicate through. Here’s an example:
In this example, the Component classes represent objects that need to communicate with each other. The Mediator class provides a centralized interface for the Component classes to communicate through. The ConcreteMediator class knows about all the Component objects and provides the sendMessage() method to send messages between them.
I like to use this pattern in a system where I have multiple components, such as interfaces to external hardware modules or subsystems. Those interface classes will inherit the Component base class and communicate one with another via the Mediator. In this manner, if one Component changes its interface, then I don’t need to go change all other Component classes — just the changing Component and the necessary portions of the Mediator.
Using the Mediator pattern allows us to reduce dependencies between objects that communicate with each other, making our code more maintainable and easier to understand. It also promotes loose coupling between objects, which makes it easier to change the way objects communicate without affecting other parts of the system.
Strategy Behavioral Pattern
The Strategy design pattern is a behavioral pattern that defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable at runtime. This pattern allows the algorithms to vary independently from clients that use them.
I have found this pattern extremely useful when I have an application that needs to support many protocols to various clients. Using the strategy pattern, I can easily swap which protocol is in use at any given time based on the client connection.
UML describing the Strategy Behavioral Pattern
C++ Example of the Strategy Behavioral Pattern
To implement this pattern, we define an abstract Strategy class that represents the interface for all algorithms. Then, we can define concrete implementations of the Strategy class for each algorithm. In my protocol case, the Strategy class was my base Protocol class. Then I had concrete protocol classes derived from the base Protocol.
In this example, the Strategy class represents the interface for all algorithms. The ConcreteStrategyA and ConcreteStrategyB classes represent concrete implementations of the Strategy class for two different algorithms.
The Context class represents the client that uses the algorithms. It has a setStrategy() method to set the current algorithm and an executeStrategy() method to execute the current algorithm.
Using the Strategy pattern allows us to change the behavior of a system at runtime by simply changing the current algorithm in the Context object. Conscious use of this pattern promotes code reuse, flexibility, and maintainability.
State Behavioral Pattern
I saved my favorite for last!
The State design pattern is a behavioral pattern that allows an object to alter its behavior when its internal state changes. This pattern is useful when an object’s behavior depends on its state, and that behavior must change dynamically at runtime depending on the state.
Essentially this boils down to defining a State base class that defines the basic structure for state information and defines the common interface, such as entry(), do(), and exit() methods.
UML describing the State Behavioral Pattern
C++ Example of the State Behavioral Pattern
In C++, we can implement the State pattern using inheritance and polymorphism. We create a State base class that represents the interface for all states. Then, we create concrete implementations of the State class for each possible state of the object. Finally, we define a Context class that acts as the context in which the state machine operates. Clients utilize the interface in the Context class to manipulate the state machine.
Here’s an example:
classState {public:
virtualvoidentry()= 0;
virtualvoiddo()= 0;
virtualvoidexit()= 0;
};
classConcreteStateA :public State {
public:
voidentry()override{
// Entry behavior for state A }
voiddo()override{
// State behavior for state A }
voidexit()override{
// Exit behavior for state A }
};
classConcreteStateB :public State {
public:
voidentry()override{
// Entry behavior for state B }
voiddo()override{
// State behavior for state B }
voidexit()override{
// Exit behavior for state B }
};
classContext {public:
Context(State* state) : state_(state) {}
voidtransitionTo(State* state){
state_->exit();
state_ = state;
state_->entry();
}
voidrequest(){
state_->do();
}
private:
State* state_;
};
Code language:C++(cpp)
In this example, the State class represents the interface for all states. The ConcreteStateA and ConcreteStateB classes represent concrete implementations of the State class for two different states.
The Context class represents the object whose behavior depends on its internal state. It has a transitionTo() method to set the current state and a request() method to trigger a behavior that depends on the current state.
You can also make use of templates in the Context class to define an addState() function. In this manner, you can enforce transitions to only a specific set of State classes and utilize custom lambda functions for the entry(), do(), and exit() functions for each state.
Using the State pattern allows us to change the behavior of an object at runtime by simply changing its internal state. This makes our code more flexible and easier to maintain. It also promotes code reuse, as we can easily add new states by implementing new State classes.
Conclusion
In conclusion, design patterns are a powerful tool in software development that can help us solve common problems and improve our code’s flexibility, maintainability, and scalability. In this post, we have explored several design patterns in C++, including the Singleton, Abstract Factory, Adapter, Decorator, Facade, Mediator, Strategy, and State patterns.
While each pattern has its unique characteristics and use cases, they all share the same goal: to provide a well-structured, reusable, and extensible solution to common software development problems. By understanding and using these patterns, we can write more efficient, robust, and maintainable code that can be easily adapted to changing requirements.
As a conscious software developer, it’s essential to keep learning and improving our skills by exploring new ideas and concepts. Design patterns are an excellent place to start, as they can provide us with a deeper understanding of software architecture and design principles. By mastering design patterns, we can become more efficient and effective developers who can deliver high-quality, scalable, and maintainable software solutions.
Additional Resources
Here are a few additional resources for diving deeper into design patterns for software development.
“Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. This book is considered the definitive guide to design patterns and is a must-read for anyone interested in the subject.
“Head First Design Patterns” by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra. This book offers a more accessible and engaging approach to learning design patterns and is ideal for beginners.
Design Patterns in Modern C++ – This Udemy course covers the basics of design patterns and shows how to apply them using modern C++ programming techniques.
C++ Design Patterns – This GitHub repository contains a collection of code examples for various design patterns in C++.
Refactoring Guru – This website provides an extensive catalog of design patterns with code examples in multiple programming languages, including C++.
Software Engineering Design Patterns – This Coursera course covers the principles and applications of design patterns in software engineering, including C++ examples.
Writing clean and effective code is essential for software developers. Not only does it make the code easier to maintain and update, but it also ensures that the code runs efficiently and without bugs. As a programming language, C/C++ is widely used in many applications, from system programming to game development. To help you write better C/C++ code, I’ve compiled a list of 10 tips from my laundry list of what makes good, clean, and effective C/C++ code. I hope these will guide you in making conscious decisions when coding, since many of these tips can be applied to other languages as well! So, whether you are an experienced C/C++ developer or just starting out, these tips will help you write cleaner, more efficient, and effective code.
Tip #1: Variable Scope Awareness
In C/C++, variables can have three different scopes: global scope, local scope, and member scope. Each of them have their place in software development and each have their own pros and cons.
My rule of thumb is this. Make everything a local variable. If I need access to it in other object methods, I promote it to a member variable. If that still doesn’t work (which is extremely rare), then I make it a static global variable. With proper software design, I have found I never need to declare a true global variable, even if I protect it with appropriate locks.
One last comment when dealing with global variables — you really should always make them const. The guidelines also state that you should always prefer scoped objects, rather than ones on the heap.
Tip #2: Use Standard Types When Available
Using standard type definitions in your C/C++ code has several benefits that can make your code more readable, portable, and maintainable. Here are some reasons why you should consider using standard type definitions in your code:
Readability: Standard type definitions like size_t, int32_t, uint64_t, etc. are self-documenting and convey a clear meaning to the reader of your code. For example, using size_t instead of int to represent the size of a container makes it clear that the variable can only hold non-negative integers, which can help prevent bugs.
Portability: Different platforms may have different data types with different sizes and behaviors. By using standard type definitions, you can ensure that your code is portable and will work consistently across different platforms.
Type safety: Using standard type definitions can help prevent bugs caused by type mismatches, such as assigning a signed int to an unsigned int variable, or passing the wrong type of parameter as a function argument.
Code maintenance: Standard type definitions can make your code easier to maintain by reducing the need for manual conversions and ensuring that the types of your variables are consistent throughout your codebase.
Overall, using standard type definitions can help make your code more readable, portable, and maintainable, and following these recommendations can help you make conscious decisions about which type definitions to use in your code.
Tip #3: Organize Related Data Into Objects
When working with complex systems, it is often worthwhile to organize sets of data into objects for three primary reasons: encapsulation, abstraction, and modularity. Each of these are powerful principles that can help improve your code.
Encapsulation
Encapsulation is a fundamental principle of object-oriented programming and can help make your code more modular and maintainable.
By organizing related data into an object, you can encapsulate the data and the operations that can be performed on it. This allows you to control access to the data and ensure that it is only modified in a safe and consistent way. In addition, you can make changes to the underlying data representation without changing the interface, which means that users of your object don’t have to change as well.
Abstraction
Objects allow you to abstract away the details of the data and provide a simplified interface for interacting with it. This can make your code easier to read and understand, as well as more resistant to changes in the underlying data representation.
Modularity
Organizing related data into an object can help you break down a large, complex problem into smaller, more manageable pieces. Each object can represent a distinct component of the system, with its own data and behavior, that can be developed and tested independently of the other components.
Finally, once you have objects that you are manipulating, you can start returning those objects from your functions. Even cooler than that, you can return tuples containing your object and status information from your methods!
Tip #4: Be Consistent in the Organization of Your Objects
When you organize your data into objects and start defining member variables and methods, be consistent in the organization of your objects. For example, declare all public interface information up front, and keep all protected and private information at the end of the class.
By declaring all private member variables and methods in a single private section, it makes the class definition much easier to read and follow. I know that when I read the GoodExample class definition that when I see the private keyword that everything coming after that keyword will be private and not accessible to me as a normal user.
Tip #5: Place All Documentation in Header Files
When you document your functions and variables, document them in the header file for one primary reason: keep the interface and implementation separate.
Keeping the interface definition of your object separate from the implementation is a solid object-oriented design principle. The header file is where you define the interface for your users. That is where your users are going to look to understand what the purpose of a function is, how it should be used, what the arguments mean, and what the return value will contain. Many times the user of your object will not have access to the source code, so placing documentation there is pointless, from an interface perspective.
Tip #6: Enforce a Coding Style
Enforcing a code style can bring several benefits to your development process, including:
Consistency: By enforcing a code style, you can ensure that your codebase looks consistent across different files and modules. This can make your code easier to read and understand, and can help reduce the amount of time developers spend trying to figure out how different parts of the codebase work.
Maintainability: A consistent code style can also make your code easier to maintain, as it can help you identify patterns and common practices that are used throughout the codebase. This can make it easier to update and refactor the code, as you can more easily find and update all instances of a particular pattern.
Collaboration: Enforcing a code style can also make it easier to collaborate with other developers, especially if they are working remotely or in different time zones. By using a consistent code style, developers can more easily understand each other’s code and can quickly identify where changes need to be made.
Automation: Enforcing a code style with clang-format can also help automate the code review process, as it can automatically format code to the desired style. This can save time and effort in the code review process, and can ensure that all code is formatted consistently, even if developers have different preferences or habits.
Industry standards: Many organizations and open-source projects have established code style guidelines that are enforced using tools like clang-format. By following these standards, you can ensure that your codebase adheres to best practices and can more easily integrate with other projects.
Tip #7: Be const-Correct in All Your Definitions
A major goal of mine when working in C and C++ is to make as many potential pitfalls and runtime bugs compiler errors rather than runtime errors. Striving to be const-correct in everything accomplishes a few things for the conscious coder:
It conveys intent about what the method or variable should do or be. A const method cannot modify an object’s state, and a const variable cannot change its value post-declaration. This can make your code safer and reduce the risk of bugs and unexpected behavior.
It makes your code more readable, as it can signal to other developers that the value of the object is not meant to be changed. This can make it easier for other developers to understand your code and can reduce confusion and errors.
It allows the compiler to make certain optimizations that can improve the performance of your code. For example, the compiler can cache the value of a const object, which can save time in certain situations.
It promotes a consistent coding style, making it easier for other developers to work with your code and reduce the risk of errors and confusion.
It makes your code more compatible with other libraries and frameworks. Many third-party libraries require const-correctness in order to work correctly, so adhering to this standard can make it easier to integrate your code with other systems.
Here are a couple of examples:
classMyConstCorrectClass{
public:
MyConstCorrectClass() = default;
void SetFlag(const bool flag) { m_flag = flag; } // Method not marked const because it modifies the state// The argument is immutable though, and is thus marked const
bool GetFlag() const { return m_flag' } // Marked as const because it does not modify state
private:
bool m_flag{false};
};
void function1(void)
{
MyConstCorrectClass A;
A.SetFlag(true);
std::cout << "A: " << A.GetFlag() << std::endl;
const MyConstCorrectClass B;
B.SetFlag(true); // !! Compiler error because B is constant
std::cout << "B: " << B.GetFlag() << std::endl;
}Code language:PHP(php)
Tip #8: Wrap Single-line Blocks With Braces
Single-line blocks, such as those commonly found in if/else statements, should always be wrapped in braces. Beyond the arguments that it increases readability, maintainability, and consistency, for me this is a matter of safety. Consider this code:
if (isSafe())
setLED(LED::OFF);Code language:C++(cpp)
What happens when I need to take additional action when the function returns true? Sleeping developers would simply add the new action right after the setLED(LED::OFF) statement like this:
if (isSafe())
setLED(LED::OFF);
controlLaser(LASER::ON, LASER::HIGH_POWER);
Code language:C++(cpp)
Now consider the implications of such an action. The controlLaser(LASER::ON, LASER::HIGH_POWER); statement gets run every single time, not just if the function isSafe() returns true. This has serious consequences, which is exactly why you should always wrap your single-line blocks with braces!
if (isSafe())
{
setLED(LED::OFF);
controlLaser(LASER::ON, LASER::HIGH_POWER);
}
Code language:C++(cpp)
Tip #9: Keep Your Code Linear — Return from One Spot
This is also known as the “single exit point” principle, but the core of it is that you want your code to be linear. Linear code is easier to read, to maintain, and debug. When you return from a function in multiple places, this can lead to hard to follow logic that obscures what the developer is really trying to accomplish. Consider this example:
std::stringTransaction::GetUUID(void)const{std::string uuid = xg::Guid(); // empty ctor for xg::Guid gives a nil UUIDif (m_library->isActionInProgress()) {return m_library->getActionIdInProgress(); }return uuid;}Code language:C++(cpp)
This seems fairly simple to follow and understand, but it doesn’t follow the single exit point principle — the flow of the method is non-linear. If the logic in this function ever gets more complex, this can quickly get harder to debug. This simple change here ensures that the flow is linear and that future modifications follow suit.
You may argue that the first function is slightly more efficient because you save the extra copy to the temporary variable uuid. But most any modern compiler worth using will optimize that copy out, and you’re left with the same performance in both.
A quick bit of wisdom — simple code, even if it has more lines, more assignments, etc. is more often than not going to result in better performance than complex code. Why? The optimizer can more readily recognize simple constructs and optimize them than it can with complex algorithms that perform the same function!
Conclusion
In this post, we covered a variety of topics related to C++ programming best practices. We discussed the benefits of using standard type definitions, the importance of organizing related data into objects, the placement of function documentation comments, the use of clang-format to enforce code style, the significance of being const-correct in all your definitions, and the reasons why it is important to wrap single-line blocks with braces and to return from only a single spot in your function.
By adhering to these best practices, C++ programmers can create code that is more readable, maintainable, and easy to debug. These principles help ensure that code is consistent and that common sources of errors, such as memory leaks or incorrect program behavior, are avoided.
Overall, by following these best practices, C++ programmers can create high-quality, efficient, and robust code that can be easily understood and modified, even as the codebase grows in size and complexity.
As software developers, we rely on variables to store and manipulate data in our programs. However, it is crucial to understand the scope of a variable and how it affects its accessibility and lifetime. In C and C++, the scope of a variable determines where in the program it can be used and for how long it will exist. In this blog post, we will be exploring the different types of scopes in C/C++ and the best practices for handling them to write clean, maintainable, and effective code.
We will look at global, local, and member scopes and how they affect the lifetime of variables. We will also discuss how to properly handle pointers, which have their own unique set of considerations when it comes to scope. By understanding the different types of scopes and how to handle them, you will be equipped to make conscious decisions about how you use variables in your code, leading to more reliable, efficient, and maintainable programs.
Variable Scope Awareness
Awareness of variable lifetimes and scopes, particularly when it comes to pointers, is critical to writing clean and effective C/C++ code. The lifetime of a variable is the period of time during which it is allocated memory and exists in the program. In C/C++, variables can have three different scopes: global scope, local scope, and member scope.
Global Scope Variables
Global scope variables are declared outside of all functions and are accessible throughout the entire program. They have a longer lifetime and persist throughout the execution of the program, but using too many global scope variables can lead to cluttered code and potential naming conflicts. However, in my mind, the more serious implications of improper use of a global variable is race conditions.
A race condition occurs when two or more threads access a shared resource, such as a global variable, simultaneously and the final result depends on the timing of the access. In a safety critical environment, where errors in the system can have severe consequences, race conditions can cause significant harm.
// Example of a global variable, including a potential race conditionint32_t g_temperature_C = 0;
voidthread1(void){// Read the temperature from the sensor g_temperature_C = ReadTemperatureFromSensor();}voidthread2(void){if ((g_temperature_C > 0) && (g_temperature_C < 70)) // !! Simple race condition {// Do some safety critical work }else {// Manage temperature out of bounds (i.e., cool down or heat up) }}Code language:C++(cpp)
In the example above, thread2 is doing some safety critical work, but only when g_temperature_C is within a certain range, which is updated in thread1. If the temperature is out of bounds, then the system needs to take a different action. The issue here is that the wrong action can lead to serious consequences, either for the safety of the system, or in the case where humans are involved, the safety of the user.
In this case, a global variable is a poor choice of scope for g_temperature_C.
If you find you do have to use global variables, you can still limit their scope to the specific compilation unit where they are defined (i.e., the file where the variable is declared). You can do this by adding the static keyword to the variable declaration. The advantage to this is that it limits the scope of the variable to just the specific module, rather than the entire program.
// Limit scope of global variable to the specific compilation unit (i.e., this file)staticint32_t g_temperature_C = 0;
Code language:C++(cpp)
Local Scope Variables
Local scope variables, on the other hand, are declared within a function or block and are only accessible within that specific scope. They have a shorter lifetime, are allocated on the stack, and are automatically deallocated from memory once the function or block has finished execution. Using local scope variables is recommended over global variables as they limit the potential for naming conflicts, allow for cleaner code, and also eliminate race conditions.
// Example of a local variable, resolving the race condition abovevoidthread2(void){int32_t l_temperature_C = ReadTemperatureFromSensor();if ((l_temperature_C > 0) && (l_temperature_C < 70)) // !! NO race condition {// Do some safety critical work }else {// Manage temperature out of bounds (i.e., cool down or heat up) }}Code language:C++(cpp)
As you can see, the race condition from using a global variable is avoided here because the variable is local and cannot be changed outside of this function.
Member Scope Variables
Member scope variables, also known as class member variables, are declared within a class and are accessible by all member functions of that class. Their scope is tied to the lifetime of the object they are a member of.
You can think of the scope of member variables to be similar to that of static global variables. Instead of being limited to the compilation unit where they are declared, they are limited to the scope of the class that they are part of. Race conditions on member variables are a real possibility. Precautions must be taken to ensure you avoid them, such as proper locking or an improved architecture to avoid the race altogether.
Properly Scoping Pointers
Pointers are a powerful tool in C and C++, allowing you to efficiently work with data objects in your programs. However, naive usage of pointers can lead to significant problems, including hard to find bugs and difficult to maintain code.
In C and C++, pointers have their own lifetime, separate from the objects they point to. When a pointer goes out of scope, the object it referenced remains in memory but is no longer accessible. When dynamically allocating memory, this leads to memory leaks where the memory is not properly deallocated, leading to a buildup of memory usage over time.
Smart Pointers
To prevent memory leaks and ensure that your programs are efficient and reliable, it is important to handle pointers with care. Modern C++ provides smart pointers types, which automatically manage the lifetime of objects and deallocate them when they are no longer needed. Using smart pointer types of std::shared_ptr and std::unique_ptr, you can be assured that when you create (and allocate) a pointer to an object, that object is constructed (and initialized if following RAII principles) and the pointer is valid. Then, when that pointer goes out of scope, the object is destructed and the memory is deallocated.
#include <memory>#include <iostream>
void PrintTemperature()
{
// Create a unique pointer to a TemperatureSensor object
std::unique_ptr<TemperatureSensor> pTS = std::make_unique<TemperatureSensor>();
// Use the unique pointer within the scope of the current function
std::cout << "Temperature: " << pTS->GetTemperature() << std::endl;
// The unique pointer goes out of scope at the end of the main function// and its dynamically allocated memory is automatically deallocated
}Code language:PHP(php)
When working with raw pointers, it’s critical to be aware of the lifetime of the objects being pointed to. For example, if the lifetime of the object ends before the pointer is deallocated, the pointer becomes a “dangling pointer”. This can cause undefined behavior, such as crashing the program or returning incorrect results. Smart pointers are typically a better choice and avoid this risk by managing the lifetime of the object themselves.
In conclusion, understanding and properly handling the scope of variables in C/C++ is a crucial aspect of writing clean, maintainable, and effective code. By becoming familiar with global, local, and member scopes, and considering the lifetime and accessibility of variables, you can make informed decisions about how to use variables in your programs.
Additionally, pointers require their own set of considerations when it comes to scope, and it is essential to handle them with care to prevent memory leaks and other issues.
By following best practices and being aware of the potential pitfalls, you can ensure that your programs are reliable, efficient, and easy to maintain.
As a software developer, managing your time effectively is crucial to unleashing your productivity. Whether you are working on a tight deadline or juggling multiple projects, having good time management skills can help you stay focused, productive and avoid burnout. In this blog post, we’ll explore some of the best time management strategies and techniques that optimize your workflow to get more done in less time. From prioritizing tasks to taking breaks, we’ll cover everything you need to know to help you take control of your time and increase your productivity as a software developer.
Over the course of my career, I’ve come across many unique styles of managing time. In my first position out of college, I worked at a company where we would support multiple contracts at once with various customers. This meant that I was bouncing between multiple projects all the time, never really able to focus on just one of them at any given moment.
During that period, I had to look to my mentors and other senior software engineers for how to best manage my time because I was feeling overwhelmed! I began to study and research how to most effectively utilize my allotted time to accomplish everything that was required of me. I’ve tried to distill the most effective strategies down to their essences here to share with you. I’ve also included some information on specific tools and techniques that I have found support these strategies well.
In my view, time management strategies serve as frameworks for structuring your tasks. These frameworks help you get a clear understanding of what needs to be achieved, and allow you to divide your work into smaller, more manageable pieces. It is important to have a solid strategy for several reasons:
Increases productivity
Reduces stress
Improves focus
Achieves goals
Promotes work-life balance
Here are the three solid frameworks or strategies that I have found useful over my career.
Weekly and Daily Planning
The things that get scheduled are the things that get done.
Vague plans produce vague goals.
World-class weeks soon morph into the sensational quarters that lead into spectacular years that generate sublime decades.
My weekly planning system to get myself organized follows these five main steps:
Connection: Reconnect with your life vision, long-term goals, and deep core values.
Reflection: Review the last week and how things went. What went well? Where can you improve? What were your key victories?
Prioritization: List out the key actions you will complete this week. These are actions that draw you closer to your goals, and also specific actions that you know yield incredible value and huge results.
Templatization: Map out each of your days for the week, roughly. List a few of the actions you prioritized for the week for each day.
Execution: Now go and do it! Each day, review your template and adjust as necessary. Map out your tasks for each day first thing in the morning to start your day organized.
This has been the number one habit that has helped me manage my time. By making these 5 steps a sacred part of my week, I’m able to keep all my plates spinning and execute each of my projects at the top of my game.
Mini-Sprints
The idea of mini-sprints is a way for software developers to apply the concept of sprints to their daily work routine. This involves dividing the week into day-long mini-sprints and focusing solely on the tasks defined for that period of time. To implement mini-sprints, plan out the tasks for each day and allocate the time accordingly. During each mini-sprint, give full attention to the tasks, while still allowing some flexibility for unexpected distractions and support requests.
The key to making mini-sprints successful is focusing on the tasks at hand, resulting in increased productivity. Tools such as Kanban boards or issue trackers can also aid in keeping track of tasks and staying on track.
Take Time to Recover
Recovery time is just as important as working time for peak performance. Working long hours to increase productivity is not always effective and can even lead to burnout. Instead, balance focused work with intentional rest and recovery. Productivity expert Robin Sharma suggests working 5 hours a day with intense focus for maximum results. Taking time for rest and renewal is essential for a healthy mind and body. Engaging in activities such as nature walks and disconnecting from technology can provide rejuvenation. By taking the time to recharge, one can work more efficiently in the long run.
Tools and Techniques
When working with those strategies to manage your time, these additional tools can be helpful in your planning and dealing with your load. You can read more about these in detail in my other post.
Parkinson’s Law states that “work expands so as to fill the time available for its completion”. Knowing this, you can set up specific procedures in your planning to help mitigate this.
Set earlier deadlines for your task, so you complete it sooner.
Set up artificial time limits to complete your task.
If using a Pomodoro (more on that later), set a limited number of cycles to complete the task.
Eisenhower Matrix
The Eisenhower Matrix is a tool for organizing tasks based on their level of importance and urgency. To use the matrix, you’ll need to rate each task as either important or unimportant, and then as either urgent or non-urgent.
The key is to focus on tasks in the top two quadrants first, delegate important but not urgent tasks if possible, and eliminate tasks that are neither important nor urgent. This will help you prioritize your tasks and focus your efforts on what’s most important and urgent.
The 80/20 Rule
The 80/20 rule, also known as the Pareto Principle, states that 20% of your actions result in 80% of your results. This can be used to prioritize tasks by ranking them based on their impact, leading to a prioritized list from top to bottom. The rule can be applied to breaking down complex problems into smaller chunks by identifying major problems, assigning categories, and scoring high level concepts within each category. By focusing on the highest scoring categories first, the 80/20 rule says that you will achieve 80% of your desired results by completing the top 20% of tasks. The rule has been found to be useful for breaking down problems and providing a clear vision of the end solution, leading to increased motivation and success.
Time Blocking
Time blocking is a method of allocating specific time slots to tasks on a to-do list. This is useful for larger tasks that take time to complete and helps ensure steady progress. The Pomodoro Technique is similar, consisting of focused 25-minute work sessions followed by 5-10 minute breaks, and longer 20-30 minute breaks after 4 sessions. Breaks are important for recovery and returning with renewed focus.
Eat the Frog
The phrase “eating the frog” is a time management technique which means starting your day by completing the most difficult and important task first, to set a productive and motivated tone for the rest of the day. The phrase originates from a quote by Mark Twain.
Another similar piece of advice, given by Admiral McRaven, is to start your day by making your bed. Even if it’s just a small task, as it can set a positive precedent for the rest of the day and lay the foundation for a productive and successful day ahead.
Tight Bubble of Total Focus
The “Tight Bubble of Total Focus” is a concept that emphasizes the importance of eliminating distractions in order to maximize productivity and efficiency. This technique requires discipline and the ability to tune out distractions by turning off your phone, closing email, and working in a quiet environment. The benefits of working in the bubble include completing tasks faster, with greater accuracy, and a deeper level of engagement and satisfaction in work.
Sometimes the tools and techniques listed here don’t apply to all situations. For example, I can’t always apply the Eisenhower Matrix and simply delegate certain tasks because they have to get done and there is no one else to delegate them to. In that case, I need to choose a different technique to get everything done. Time and experience with these techniques will help you decide which is appropriate for the given circumstance.
Effective time management is crucial for software developers as it allows them to increase their productivity and efficiency. By utilizing the tools and techniques mentioned in this guide, software developers can streamline their work processes, prioritize their tasks, and minimize distractions. Whether it’s detailed planning, time blocking, or the Pomodoro Technique, each tool serves a unique purpose and can be customized to fit your specific needs. Remember, productivity is about finding what works best for you, so don’t be afraid to experiment with different tools and techniques until you find the ones that resonate with you. With the right approach, you can unleash your productivity, accomplish more in less time, and achieve your professional and personal goals with ease.