Tag: Efficiency

  • Mastering Variable Scopes in C/C++: Best Practices for Clean and Effective Code

    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 condition
    int32_t g_temperature_C = 0;
    
    void thread1(void)
    {
      // Read the temperature from the sensor
      g_temperature_C = ReadTemperatureFromSensor();
    }
    
    void thread2(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)
    static int32_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 above
    void thread2(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.

    #include <iostream>
    
    class TemperatureSensor
    {
    public:
      TemperatureSensor() = default;
    
      void GetTemperature()
      {
        m_temp_C = ReadTemperatureFromSensor();
        return m_temp_C;
      }
    
    private:
      int32_t m_temp_C{0};
    };
    
    int main()
    {
      TemperatureSensor sensor;
      std::cout << "Temperature: " << sensor.GetTemperature() << std::endl;
      std::cout << "Temperature: " << sensor.GetTemperature() << std::endl;
    
      return 0;
    }Code language: PHP (php)

    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.

  • Unleash Your Productivity: An Essential Guide of 9 Effective Tools for Software Developers

    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.

    Photo by Vanessa Garcia on Pexels.com
    Photo by Vanessa Garcia on Pexels.com

    Strategies

    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:

    1. Increases productivity
    2. Reduces stress
    3. Improves focus
    4. Achieves goals
    5. Promotes work-life balance

    Here are the three solid frameworks or strategies that I have found useful over my career.

    Weekly and Daily Planning

    1. The things that get scheduled are the things that get done.
    2. Vague plans produce vague goals.
    3. World-class weeks soon morph into the sensational quarters that lead into spectacular years that generate sublime decades.
    Robin Sharma, Chapter 61 of The Everyday Hero Manifesto

    My weekly planning system to get myself organized follows these five main steps:

    1. Connection: Reconnect with your life vision, long-term goals, and deep core values.
    2. Reflection: Review the last week and how things went. What went well? Where can you improve? What were your key victories?
    3. 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.
    4. 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.
    5. 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.

    Photo by Miguel Á. Padriñán on Pexels.com
    Photo by Miguel Á. Padriñán on Pexels.com

    Parkinson’s Law

    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.