CS 161: Mastering Intro to Programming - Problem-Solving

Module 1: Problem-Solving

Introduction

Creating a program or tangible product can be very intimidating, and I’m the first one to admit it. In my experience, the approach known as the “engineering design process” or simply as the “problem-solving process” has been instrumental in navigating from an idea to a final product in any situation.

In summary, these are the steps:

  1. Understand the Requirements

  2. Design Tests

  3. Outline Your Approach

  4. Translate Your Plan into Code

  5. Run Your Tests

  6. Debug Your Code

  7. Reflect on Your Process

A simple story: Back in 2010, I took my first engineering class in the US, and we had to come up with a system that would deliver an egg to a box, using basic items. We had to use the “problem-solving process.” At first, I thought it was a waste of my time and money. I didn’t see how this could be valuable. Long story short, I didn’t see this differently until my senior year, doing my senior project with Texas Instrument. We had to design and build a low-budget large-scale 3D printer, like the ones that print houses nowadays! It was 2015, and the cheapest large-scale 3D printer was around $300,000. We were given $2,000 per team, so we were three teams, and we had a total of $6,000. Of course, we were clueless and lost, and thought the creator of the project had lost his mind. They matched us with an industry advisor and a faculty advisor, and the first thing that came out of their mouth was to use the “problem-solving process.” After learning it, we ended up doing it under budget! How? Simply, using that process that I thought was a waste of time and money. Seeing faculty and industry advisors use the “problem-solving process” gave me relief that I didn’t waste my money.

Then around 2017, an opportunity to build a reverse osmosis system for the Galapagos Island came to me. I had some basic knowledge of the water filtration system, and I knew how I could go from idea to the final product. I talked to someone with 20 years of experience and worked together, using the same “problem-solving process” to design and build a reverse osmosis system for the Galapagos Island.

Since then, I was convinced that this was the way to go, especially if you are trying to create something new, which most of the time happens in the software world. There aren’t guidelines or products that you can use as a go-by. Maybe you will be able to use certain parts, but once you put them together, you will have to test, reflect, and iterate until you get the final product you are looking for.

As an engineer, I have used this process to solve and create different products. It works every time. If there is something that is most of the time overlooked, it's the engineering design process.

Understanding the Requirements

This is one of the most challenging parts since it represents your end goal. When people talk to me about ideas, they often don't even know what they want. So it is helpful to write down the requirements. Going through the project specifications and putting them in the form of a checklist can provide clear instructions and ensure everyone is on the same page.

Design Tests:

Testing is a critical aspect of the problem-solving process, ensuring that your solution meets specified requirements and functions as intended. Here are key principles and strategies for designing effective tests:

  1. Identify Test Cases:

    • Start by identifying various test cases that represent different scenarios and functionalities of your program. Consider special cases, boundary values, path coverage, edge cases, unit tests, and integration tests.

  2. Establish Expected Results:

    • Define the expected outcomes or results for each test case. This step helps determine whether your program behaves correctly under different conditions.

  3. Functional Testing:

    • Conduct functional testing to verify that each component of your program performs its intended function accurately. This includes input validation, output verification, and error handling.

  4. Regression Testing:

    • Implement regression testing to ensure that new code changes do not adversely impact existing functionalities. Re-run previously successful tests to validate system stability.

  5. Automated Testing:

    • Consider using automated testing tools and frameworks to streamline the testing process, especially for repetitive or complex test cases. Automation can save time and improve test coverage.

  6. Documentation:

    • Document your test cases, including input data, expected results, actual results, and any deviations or issues encountered during testing. This documentation serves as a reference for future testing and troubleshooting.

  7. Iterative Testing and Refinement:

    • Adopt an iterative approach to testing, continuously refining and expanding your test suite based on feedback, bug reports, and evolving requirements. Regular testing iterations help catch and address issues early in the development cycle.

By following these principles and incorporating comprehensive testing practices into your problem-solving process, you can ensure the reliability, functionality, and quality of your software solutions.

Outline Your Approach:

Before diving into coding, it's essential to outline a structured approach to solving the problem. Here are the key steps to effectively outline your approach:

  1. Define the Problem: Clearly define the problem statement or requirements. What is the goal of your program? What inputs will it take, and what outputs should it produce? Understanding the problem thoroughly is the foundation of your approach.

  2. Break it Down: Divide the problem into smaller, more manageable sub-problems or tasks. This process, known as stepwise refinement or decomposition, simplifies complexity and allows you to focus on solving one part at a time.

  3. Pseudocode or Flowchart: Use pseudocode or flowcharts to outline the logical flow of your solution. Pseudocode is a high-level description of your algorithm using natural language and simple code-like constructs. Flowcharts visually represent the sequence of steps and decision points in your algorithm.

  4. Identify Algorithms and Data Structures: Choose suitable algorithms and data structures based on the problem requirements. Consider factors such as efficiency, scalability, and readability.

  5. Modular Design: Break your code into reusable functions or modules. Each function should perform a specific task, promoting code readability, maintainability, and reusability.

  6. Plan for Input and Output: Design input validation mechanisms to handle invalid or unexpected inputs gracefully.

  7. Consider Error Handling: Implement robust error handling mechanisms to manage unexpected situations and prevent program crashes.

  8. Optimization and Scalability: Consider optimization techniques to improve the efficiency and performance of your solution.

  9. Documentation and Comments: Document your code and add comments to explain the logic, algorithms, and critical sections of your code.

  10. Review and Refinement: Review your approach and pseudocode/flowcharts to ensure they align with the problem requirements.

By outlining your approach methodically, you set a solid foundation for coding and implementation, leading to a more efficient and effective problem-solving process.

Work Through Examples by Hand:

  • Engage in manual problem-solving by working through examples on paper or using a whiteboard. This hands-on approach helps you understand the problem's intricacies and identify patterns or algorithms.

Translate Your Outline into Code:

Now that you have outlined a structured approach and developed pseudocode or flowcharts, it's time to translate your plan into actual code. Follow these steps to effectively implement your solution:

  1. Start with a Framework: Set up the basic framework of your program.

  2. Implement Subroutines/Functions: Translate each step from your pseudocode/flowcharts into individual subroutines or functions.

  3. Code in Small Increments: Implement code in small increments and test each part as you go.

  4. Use Descriptive Variable Names: Use meaningful and descriptive variable names.

  5. Follow Coding Standards: Adhere to coding standards and style guidelines.

  6. Handle Input and Output: Incorporate code to handle input data and generate output.

  7. Implement Algorithmic Logic: Translate the algorithmic logic outlined in your pseudocode/flowcharts into code.

  8. Include Error Handling: Implement error handling mechanisms to catch and handle runtime errors or exceptions.

  9. Test Incrementally: Test each section of code as you implement it to verify correctness and functionality.

  10. Document as You Code: Add inline comments and documentation within your code.

  11. Optimize and Refactor: Review your code for potential optimizations and improvements.

  12. Review and Test End-to-End: Conduct comprehensive testing of the entire program to ensure all components work together seamlessly.

By systematically translating your outline into code and following best practices, you can create a well-structured, readable, and functional program that effectively solves the problem at hand.

Run Your Tests:

After translating your plan into code, the next crucial step is to thoroughly test your program to ensure it functions correctly and meets all requirements. Running tests helps you identify and fix any issues or bugs in your code before deploying it.

  1. Prepare Test Cases:

    • Refer back to the test cases you designed during the "Design Tests" phase. These test cases should cover a range of scenarios, including normal inputs, edge cases, boundary values, and potential error conditions.

  2. Execute Test Cases:

    • Run your program with each test case individually. Ensure that you provide inputs as specified in the test cases and capture the corresponding outputs.

  3. Verify Expected Outputs:

    • Compare the outputs generated by your program with the expected results defined in your test cases. Verify that the program behaves as intended for each test scenario.

  4. Handle Errors and Exceptions:

    • Pay attention to any errors or exceptions encountered during testing. Debug these issues systematically, using error messages, debugging tools, or print statements to pinpoint the root cause of the problem.

  5. Regression Testing:

    • Perform regression testing by re-running previously passed test cases after making changes or fixing bugs. Ensure that existing functionalities continue to work as expected without regression or unintended side effects.

  6. Edge Case Testing:

    • Focus on testing edge cases and extreme inputs that push the limits of your program's capabilities. Verify that the program handles such cases gracefully and produces correct outputs without crashing or malfunctioning.

  7. Integration Testing:

    • If your program interacts with external systems, databases, or APIs, conduct integration testing to validate the interactions and data exchange. Ensure seamless integration and functionality across all interconnected components.

  8. Automated Testing (Optional):

    • Consider automating your testing process using testing frameworks or scripts. Automated testing can save time, ensure consistency in test execution, and facilitate continuous integration and deployment workflows.

  9. Document Test Results:

    • Maintain a record of your test results, including passed tests, failed tests, and any issues encountered. Documenting test outcomes helps in tracking progress, communicating with team members, and identifying areas for improvement.

  10. Iterate and Improve:

    • Based on the test results and feedback, iterate on your code to address any identified issues, optimize performance, enhance functionality, and improve overall code quality. Continuous iteration and improvement are essential for delivering a robust and reliable solution.

By running comprehensive tests and addressing any issues proactively, you can enhance the reliability, functionality, and user experience of your program, ensuring its readiness for deployment in real-world scenarios.

Debug Your Code:

Debugging is a critical phase in the software development process where you identify and fix errors, bugs, or unexpected behaviors in your code. Effective debugging techniques can save you significant time and effort in troubleshooting and ensuring the reliability of your program.

  1. Understand the Problem:

    • Before diving into debugging, ensure that you have a clear understanding of the problem or issue you are trying to resolve. Reproduce the error consistently to understand its scope and impact on your program's functionality.

  2. Isolate the Issue:

    • Use debugging tools, such as integrated development environment (IDE) debuggers, logging statements, or print statements, to pinpoint the location and cause of the problem. Narrow down the scope of your investigation to specific sections of code or variables where the issue might originate.

  3. Review Error Messages:

    • Pay attention to error messages, warnings, or stack traces provided by your programming environment or runtime environment. Error messages often provide valuable clues about the nature of the problem and its potential causes.

  4. Check Inputs and Outputs:

    • Verify that inputs provided to your program are valid and meet the expected format or criteria. Check the outputs generated by your code against the expected results defined in your test cases or specifications.

  5. Use Breakpoints:

    • Utilize breakpoints in your IDE debugger to pause code execution at specific lines or conditions. This allows you to inspect variable values, control flow, and program state at runtime, helping you identify discrepancies or unexpected behaviors.

  6. Step Through Code:

    • Step through your code line by line using debugging tools. Follow the execution flow and observe how variables change values, conditions are evaluated, and functions/methods are called. Identify any deviations from the expected behavior.

  7. Inspect Variable Values:

    • Examine the values of variables, data structures, and objects during runtime. Look for inconsistencies, unexpected changes, or null/undefined values that may indicate a bug or logical error in your code.

  8. Use Logging and Print Statements:

    • Insert logging statements or print statements strategically throughout your code to output diagnostic information, variable values, or program state. Analyze the logged output to trace the execution path and identify problematic areas.

  9. Reproduce and Test:

    • Reproduce the error or issue consistently using the identified test cases or scenarios. Make sure to test your fixes and modifications rigorously to ensure that the problem is resolved and that no new issues are introduced.

  10. Document Changes and Solutions:

    • Keep track of the changes you make during debugging, including code modifications, fixes, and workarounds. Document the root cause of the issue, the debugging process, and the solutions implemented for future reference and knowledge sharing.

  11. Seek Collaboration and Feedback:

    • Don't hesitate to seek help or collaborate with peers, mentors, or online communities when debugging complex issues. Share your code, error messages, and debugging insights to gain fresh perspectives and valuable feedback.

  12. Learn from Debugging:

    • Treat debugging as a learning opportunity to improve your coding skills, problem-solving abilities, and understanding of programming concepts. Reflect on the debugging process, the lessons learned, and apply them to future projects to avoid similar issues.

By following these debugging practices and strategies, you can effectively identify and resolve issues in your code, ensuring the reliability, functionality, and performance of your software solutions.

Reflect on Your Process:

Reflection is a valuable practice in the software development lifecycle that allows you to evaluate your problem-solving approach, coding strategies, and overall learning experience. Taking time to reflect on your process enables you to identify strengths, areas for improvement, and valuable insights for future projects.

  1. Evaluate Problem-Solving Strategies:

    • Reflect on the problem-solving strategies you employed during the development process. Consider the effectiveness of techniques such as stepwise refinement, working through examples, and outlining your approach. Identify which strategies worked well and contributed to your success.

  2. Assess Code Structure and Design:

    • Evaluate the structure, organization, and design of your codebase. Consider aspects such as modularity, readability, maintainability, and adherence to coding standards. Assess whether your code is well-structured, easy to understand, and scalable for future enhancements.

  3. Review Testing Practices:

    • Review your testing practices and methodologies. Evaluate the comprehensiveness of your test cases, the accuracy of expected results, and the effectiveness of test-driven development (TDD) or test automation. Identify any gaps in testing coverage or areas where improvements can be made.

  4. Document Learning and Insights:

    • Document key learnings, insights, and discoveries made during the development process. Capture lessons learned from debugging sessions, challenges faced, and innovative solutions implemented. Documenting your learning journey helps reinforce knowledge and facilitates knowledge sharing with others.

  5. Seek Feedback and Collaboration:

    • Seek feedback from peers, mentors, or stakeholders regarding your code, approach, and final deliverables. Solicit constructive criticism, suggestions for improvement, and alternative perspectives. Collaborate with others to gain diverse insights and enhance your problem-solving skills.

  6. Identify Successes and Challenges:

    • Identify successes, accomplishments, and milestones achieved during the development cycle. Celebrate achievements such as resolving complex bugs, implementing new features, or meeting project deadlines. Acknowledge challenges faced, setbacks encountered, and lessons learned from failures.

  7. Set Goals for Improvement:

    • Based on your reflections and assessments, set specific goals for improvement in areas such as coding proficiency, problem-solving skills, testing techniques, and collaboration practices. Define actionable steps and timelines for achieving your improvement goals.

  8. Iterate and Apply Feedback:

    • Iterate on your code, design, and development processes based on feedback received and insights gained. Apply feedback and suggestions from peers, mentors, or code reviews to refine your solutions, optimize performance, and enhance code quality.

  9. Continuously Learn and Adapt:

    • Embrace a growth mindset and a commitment to continuous learning and adaptation. Stay updated with new technologies, programming languages, and best practices in software development. Engage in learning opportunities, workshops, and online resources to expand your knowledge and skills.

  10. Document Reflections and Action Items:

    • Document your reflections, action items, and improvement plans in a structured format. Maintain a development journal, retrospective notes, or project documentation to track progress, monitor changes, and revisit insights over time. Use your documentation as a reference for future projects and personal growth.

  11. Celebrate Achievements and Progress:

    • Celebrate achievements, milestones, and progress made throughout the development journey. Recognize your efforts, perseverance, and dedication to continuous improvement. Share successes with your team, peers, or online communities to inspire and motivate others.

  12. Stay Open to Feedback and Adaptation:

    • Remain open to feedback, criticism, and new perspectives from others. Use feedback as an opportunity for growth, learning, and refinement of your skills and practices. Embrace adaptability, flexibility, and a willingness to evolve as a software developer.

By reflecting on your process, learning from experiences, and applying feedback, you can enhance your problem-solving capabilities, improve code quality, and achieve greater success in your software development endeavors.




Next
Next

Navigating CS 161 at OSU - Intro to Programming