Sourav Goyal’s Post

"IF-ELSE" CHAINS ARE A CODE SMELL. 👃 One of the biggest leaps from "Junior" to "Senior" engineering is realizing that if statements are often a sign of missing architecture. In many of my projects, I frequently deal with logic that needs to change behavior based on context. • Sometimes we need the Shortest Path (Dijkstra). • Sometimes we need the Fastest Calculation (Greedy Best-First). • Sometimes we need to avoid specific obstacles (Custom A*). The novice approach is a massive switch statement or a 50-line if-else block. The engineering approach is the Strategy Pattern. By defining a common Interface (e.g., IRouteFinder), we can inject the specific algorithm into the solver at runtime. The solver doesn't care how the path is found, only that it is found. The C++ Optimization: Classically, we do this with Virtual Functions (Runtime Polymorphism). But in modern C++ (17+), I’ve been experimenting with std::variant and std::visit. This allows us to achieve similar "swappable logic" but often with better cache locality and no v-table overhead. It’s a technique I see often in High-Frequency Trading systems to keep things "static" where possible. I’m currently refactoring my personal Python project to use this pattern for its scoring engine, swapping "League Rules" without touching the core match logic. What’s your favorite Design Pattern to clean up messy logic? Strategy? Observer? Factory? #SoftwareArchitecture #DesignPatterns #Cplusplus #Python #CleanCode #Refactoring

Generally it’s a recommended principle of “clean code” to extract huge code in an if-else into a function (like Java method). At the end the if-ellse becomes readable. The strategy pattern can be beneficially in the context of your example because it abstracts things away. It allows of writing common tests. ❌What is redundant in your post is offensing people calling them juniior or senior.

Like
Reply

If i understood it correctly then you are just making 3 separate class files and calling them at run time as per the use case instead of writing the code under the if-else chain! You would still need to check the use-case and then call the required class accordingly, isn't this the case? How are you determining and checking the use-case without if-else?

Much of ML is massive if/else blocks (see: decision trees, random forest). It's if/elses all the way down man!

I'm reading this because Cursor is implementing a factory pattern for me right now. Whoops! It's done. Back to work.

Like
Reply

Sourav Goyal great explanation of the issue or starting point to justify the need for strategy design pattern public List<Point> findPath(Point start, Point end, Grid grid, String mode) { if (mode.equals("dijkstra")) { PriorityQueue<Node> queue = new PriorityQueue<>(); } else if (mode.equals("greedy")) { PriorityQueue<Node> queue = new PriorityQueue<>(); } else if (mode.equals("astar")) { PriorityQueue<Node> queue = new PriorityQueue<>(); } else if (mode.equals("astar_obstacles")) { PriorityQueue<Node> queue = new PriorityQueue<>(); } return result; }

Sourav Goyal great mental model for understanding the strategy design pattern // Strategy Interface interface IRouteFinder { List<Point> findRoute(Point start, Point end, Grid grid); } // Concrete Strategies class DijkstraRouteFinder implements IRouteFinder { @Override public List<Point> findRoute(Point start, Point end, Grid grid) { // Dijkstra implementation return new ArrayList<>(); // placeholder } } class GreedyBestFirstRouteFinder implements IRouteFinder { @Override public List<Point> findRoute(Point start, Point end, Grid grid) { // Greedy best-first implementation return new ArrayList<>(); // placeholder } } class CustomAStarRouteFinder implements IRouteFinder { @Override public List<Point> findRoute(Point start, Point end, Grid grid) { // Custom A* implementation return new ArrayList<>(); // placeholder } }

Sourav Goyal great mental model for understanding the strategy design pattern // Context (The Solver) class PathSolver { private IRouteFinder routeFinder; public PathSolver(IRouteFinder routeFinder) { this.routeFinder = routeFinder; } public void setRouteFinder(IRouteFinder routeFinder) { this.routeFinder = routeFinder; } public List<Point> solve(Point start, Point end, Grid grid) { return routeFinder.findRoute(start, end, grid); } } // Usage class Main { public static void main(String[] args) { Grid grid = new Grid(); Point start = new Point(0, 0); Point end = new Point(10, 10); // No if statements - just swap strategies PathSolver solver = new PathSolver(new DijkstraRouteFinder()); List<Point> path1 = solver.solve(start, end, grid); solver.setRouteFinder(new GreedyBestFirstRouteFinder()); List<Point> path2 = solver.solve(start, end, grid); solver.setRouteFinder(new CustomAStarRouteFinder()); List<Point> path3 = solver.solve(start, end, grid); } }

The Unix design philosophy prevents this from needing to be solved.

Like
Reply

That really depends on the language. Rust, for example, forces fairly heavy use of if or match statements to deal with errors, optionals and enums. In Java, the smell exists when you’re using conditional logic to examine the sub-types. That does scream for polymorphism. But if and switch statements are not inherently evil.

See more comments

To view or add a comment, sign in

Explore content categories