The technical debt of language loyalty We often treat programming languages like sports teams, wearing our Python or Rust badges with a sense of tribal pride. But tying your professional identity to a specific syntax creates a dangerous blind spot. When you define yourself as a "Python Developer," any criticism of the language feels like a personal attack. This defensive posture stops you from evaluating tools objectively and prevents you from seeing where a project actually needs a different approach. Seniority isn't about how many dunder methods you know; it's about the ability to choose the right tool for the specific job without an ideological filter. Living with Python's inherent flaws Let's be honest: Python has real problems. Semantic whitespace can feel fragile to those raised on C++ or Java. The performance lag is undeniable when compared to Go, and the packaging ecosystem has historically been a fragmented mess of virtual environments and requirements files. Even with modern improvements like uv, the underlying inconsistencies in class design and the verbosity of type hints remain. Many of the reasons to dislike the language are just as true today as they were a decade ago. Outcomes over ideology Despite these flaws, Python remains a powerhouse because of its utility in the "glue code" that runs the modern world. If 90% of your work involves orchestrating APIs, database queries, and LLM integrations, the bottleneck is almost never execution speed—it's network latency. The massive ecosystem and ease of setup make it the pragmatic choice for machine learning and automation. Actionable steps for the senior mindset Start by de-coupling your worth from your stack. Practice "tool-agnostic" design by focusing on architecture that could theoretically be implemented in any language. Don't judge other developers for their choices, as you rarely know their specific team constraints or existing infrastructure requirements. Finally, give yourself permission to use multiple languages across different projects. You don't have to marry your tools; you just need them to solve the problem at hand.
Java
Languages
ArjanCodes (3 mentions) references the early Java era in videos such as "Python Properties vs Methods" to discuss the evolution of object-oriented programming and the impacts of deep inheritance hierarchies.
- Apr 3, 2026
- Feb 20, 2026
- Apr 12, 2024
- Mar 12, 2024
- Oct 13, 2023
Modern Software Design: Beyond the Python Hype When we look at the trajectory of software development in 2023, it is easy to get swept up in the latest library or the newest language version. However, the real work of a developer remains centered on the architecture of logic. **Software design is the art of keeping things manageable.** While much of my recent work focuses on Python, the principles of clean code are largely language-agnostic. Whether you are working in Rust, TypeScript, or Java, the challenge remains the same: how do we structure our systems so they do not collapse under their own weight as they grow? One of the most frequent requests I receive is for more content on Artificial Intelligence and Machine Learning. While these are undoubtedly the "noisy" sectors of our industry right now, I have intentionally kept my focus on the niche of software design. There is a specific reason for this. In the rush to implement neural networks or data pipelines, many developers abandon the fundamental practices that make software sustainable. A machine learning model wrapped in spaghetti code is a liability, not an asset. My goal is to ensure that as we move into these complex domains, we carry with us the habits of clean functions, decoupled classes, and robust testing. The Protocol Shift: Inheritance vs. Composition One of the more nuanced discussions in modern development involves the transition away from heavy inheritance hierarchies. In the past, Object-Oriented Programming (OOP) often forced us into rigid parent-child relationships between classes. Today, I find myself moving toward a more functional approach, favoring protocols and composition over abstract base classes. This is a significant shift in how we think about interfaces. In Python, the use of Protocols allows for structural subtyping, or "duck typing." This means we define what an object *does* rather than what it *is*. If an object has the required methods, it satisfies the protocol. This leads to much cleaner code because it removes the need for a central inheritance tree that every developer must understand to make a change. When you define a protocol close to the function that uses it, you are documenting the requirements of that function explicitly. This is not just a syntax choice; it is a design philosophy that prioritizes flexibility and reduces the cognitive load on the developer. We must also be careful about where we place our business logic. A common mistake is overloading constructors with complex operations. Creating an object should be lightweight. If you bury heavy logic in a `__init__` method, you lose control over the execution flow. You cannot easily create objects for testing or previewing without triggering those side effects. By keeping constructors thin and moving logic into dedicated methods or factory functions, you gain the ability to manage state more effectively, which is essential for building responsive applications. Navigating the Ecosystem: Tools, Frameworks, and Risks Choosing a tech stack is rarely about finding the "best" tool; it is about managing risk. Take the choice between FastAPI and newer contenders like Starlite. FastAPI has become a staple because of its speed and developer experience, but it is largely maintained by one person. This creates a "bus factor" risk. If the primary maintainer disappears, the ecosystem stalls. Conversely, a newer framework might have more maintainers but lacks the massive community support, plugin ecosystem, and battle-tested stability of the market leader. For production environments, I always lean toward stability. It is fun to experiment with the latest web framework or a new language like Mojo for a hobby project, but when users' data and company revenue are on the line, you want the tool that has the most eyes on its GitHub issues. The same applies to deployment. Docker has become non-negotiable for the modern developer because it solves the "it works on my machine" problem. Understanding how your code lives in a container and how that container interacts with a cloud provider like AWS is no longer a specialty—it is a baseline requirement for being an effective software engineer. The AI Assistant: GitHub Copilot and the Future of Work There is a lot of anxiety surrounding ChatGPT and GitHub Copilot. People ask if these tools will replace us. My experience has been the opposite: they make us more powerful, provided we remain the architects. GitHub Copilot is excellent at generating boilerplate or suggesting the implementation of a standard algorithm. It saves time on the repetitive parts of coding, allowing the developer to focus on the high-level design and the integration of components. However, a chat interface is not the future of programming. Coding is about context and overview. You need to see how a change in one module affects the entire system. AI tools struggle with this holistic view. They are optimized for the immediate snippet. As an engineer, your value is not in your ability to type syntax—it is in your ability to define the problem and verify that the solution is correct. We are moving from being "code writers" to "code reviewers" and "system architects." This shift requires even stronger analytical skills and a deeper understanding of design patterns, as you must be able to spot when the AI-generated code is subtly wrong or architecturally unsound. Balancing the Grind: Career Growth and Learning One of the hardest parts of being a developer is the constant feeling that you are falling behind. New frameworks emerge every week, and the industry's pace is relentless. My advice is to find a way to incorporate learning into your professional life rather than sacrificing every evening and weekend to the grind. If you are learning new skills, you are becoming a more valuable asset to your employer. It should be a win-win scenario. For those looking to transition into the field or move into management, remember that credentials matter less than demonstrated skill. While a Computer Science degree provides a solid foundation, many successful engineers come from diverse backgrounds like electrical engineering or self-taught paths via coding schools. What matters most is the ability to break down complex problems and communicate solutions. If you want to move into management, start by taking an advisory role in technical decisions. Show that you understand the business impact of code, not just the technical elegance. The most successful lead developers are those who can bridge the gap between a messy business requirement and a clean technical implementation. Ultimately, software development is a long game. Whether you are dealing with workplace politics, choosing between Scrum and Kanban, or debating the merits of Graph Databases, the key is to stay curious and methodical. Don't be afraid to step out of your comfort zone—it is the only place where real growth happens. Keep building, keep breaking things, and most importantly, keep designing with the future in mind.
Jan 10, 2023Beyond the Syntax: The Emotional Architecture of Coding Software development often masquerades as a purely logical pursuit, a series of binary choices dictated by compilers and interpreters. However, when we strip away the Python scripts and the TypeScript interfaces, we find that the most complex architecture we deal with isn't our codebase—it's the human ego. One of the most difficult transitions for a developer moving from an academic or individual contributor role into entrepreneurship or senior leadership is the realization that technical brilliance is secondary to user empathy. In the hallowed halls of academia, success is often measured by the weight of one's own name on a research paper. In the real world of building products, the ego is a liability. Starting a company or leading a project requires a fundamental shedding of the self. If you remain too stubborn to reconsider a technology choice because you've staked your identity on it, the market will eventually humble you. High-level software design is less about being right and more about being a perpetual learner. When customers tell you a feature doesn't work or a technology choice feels clunky, they aren't attacking your intelligence; they are providing the raw data necessary for your next iteration. This shift from an ego-driven developer to a learner-driven engineer is the first step toward true seniority. It transforms every bug and every failed startup into a data point rather than a personal failure. Decoupling Logic with Protocols and Abstractions In the technical trenches, we often face the challenge of managing complexity across disparate systems. A common hurdle involves handling objects that share some traits but diverge significantly in others—like different sales channel parsers in an e-commerce engine. While many reach for abstract base classes, Python offers a more flexible tool: Protocols. Using structural subtyping, or 'duck typing' with a formal definition, allows us to decouple our code from specific third-party implementations. Imagine you are using a library you didn't write. You want to enforce a specific interface, but you cannot force the library's classes to inherit from your abstract base class. This is where Protocols shine. They allow you to define what an object should *do* rather than what it *is*. However, this flexibility isn't free. When you abandon explicit inheritance, you lose some of the immediate safety nets provided by static type checkers. It’s a classic trade-off: you gain the ability to integrate diverse systems without a rigid hierarchy, but you must be more disciplined in how you verify those interactions. This reflects a broader principle in software design: the best tools don't eliminate responsibility; they provide more precise ways to manage it. The API Dilemma: Structure vs. Integration Choosing a communication layer for your application is rarely a battle between 'good' and 'bad' technology, but rather a calculation of control. trpc has gained massive traction for its end-to-end type safety, especially in the Node.js and TypeScript ecosystems. It creates a seamless bridge between the front end and the back end, making the two feel like a single, unified code space. But this tight integration is a double-edged sword. If you control both ends of the wire, trpc is a powerhouse of productivity. However, if your goal is to build a public API or a service that third parties will consume, that tight coupling becomes a cage. In those scenarios, REST or GraphQL remain the gold standards. GraphQL, in particular, provides a structured query language that allows clients to request exactly what they need, nothing more and nothing less. It effectively eliminates the need for complex state management libraries like Redux, which often introduce more boilerplate than they solve. For many modern applications, using Apollo Client with GraphQL handles the heavy lifting of caching and state synchronization, allowing developers to focus on building features rather than plumbing. The decision isn't about which technology is 'better,' but about where you want to draw the boundaries of your system. Managing the Risk of the New: From AI to Infrastructure We are currently witnessing a seismic shift in developer tooling with the advent of ChatGPT and GitHub Copilot. It is tempting to view these as a replacement for the human programmer, but a more accurate view is that they are an evolution of the Integrated Development Environment (IDE). The chat interface itself is likely a transitional phase. The future lies in deep integration—tools that don't just write code for you, but identify edge cases, suggest unit tests, and explain legacy spaghetti code in real-time as you type. When starting any new project, whether it involves AI or traditional CRUD operations, the most vital skill is risk mitigation. Don't start by polishing the user interface. Start by attacking the most challenging technical uncertainty. If your app relies on a specific Cloud integration or a complex database relationship in MongoDB, build a 'walking skeleton' that connects those pieces first. By proving the core architecture early, you avoid the nightmare of discovering a fundamental limitation after weeks of work. This proactive approach to risk is what separates the veterans from the hobbyists. It ensures that when you finally do sit down to write the business logic, you’re building on a foundation of certainty rather than hope. The Senior Mindset: Horizon and Responsibility What truly defines a senior engineer? It isn't just years of experience or the number of languages on a resume. It is the width of their horizon. A junior developer sees a ticket and thinks about the specific lines of code needed to close it. A senior developer sees a ticket and thinks about how that change will affect the database schema, the CI/CD pipeline, and the user's mental model of the application. They understand that every line of code is a liability, and sometimes the best way to solve a problem is by deleting code rather than adding it. Seniority also involves a transition into mentorship and organizational awareness. It means being the person who can bridge the gap between technical constraints and business goals. If you're a fresh graduate feeling stuck in the 'experience trap,' remember that companies aren't just looking for someone who knows Python 3.11 syntax. They are looking for a learning mindset. Show that you can take a vague requirement and turn it into a structured plan. Show that you understand the 'why' behind SOLID principles, even if you haven't mastered every design pattern yet. Professional growth is an iterative process, much like refactoring. You start with something that works, and then you spend the rest of your career making it cleaner, faster, and more empathetic.
Dec 6, 2022Object-oriented programming (OOP) often gets a bad reputation. Critics argue it leads to bloated, slow, and unnecessarily complex codebases. Much of this frustration stems from the early Java era, where deep inheritance hierarchies and rigid class structures became the industry standard. However, the problem isn't the paradigm itself, but how we apply it. By shifting our perspective, we can use objects to create more readable, maintainable software without falling into the traps of the past. The Hybrid Paradigm Approach You don't have to choose between functional and object-oriented styles. In fact, the most elegant Python code often blends the two. While classes excel at representing data structures and state, pure functions are often better for logic that doesn't require a persistent internal state. Using tools like the functools package allows you to keep your logic lean while leveraging classes where they actually add value. Separating Data from Behavior A common mistake is trying to make every class a "do-it-all" entity. A more effective strategy involves Categorizing classes as either data-oriented or behavior-oriented. Data-oriented classes, like Data Classes, should focus on structuring information. Behavior-oriented classes should focus on actions. If a behavior-focused class doesn't require much internal data, consider turning it into a simple function or a module. This separation prevents the "kitchen sink" anti-pattern where a single object becomes impossible to manage. Flattening Inheritance Hierarchies Deep inheritance creates a cognitive mess. When you find yourself three or four levels deep in a subclass, tracking where a specific behavior originates becomes a nightmare. Instead of using inheritance to share code, use it to define interfaces. Tools like Protocols or Abstract Base Classes allow you to define what an object should do without forcing rigid, brittle relationships between different parts of your code. Decoupling with Dependency Injection Hard-coding dependencies inside your classes makes them impossible to test. If a function creates its own Stripe payment handler internally, you can't easily swap it for a mock during testing. By passing dependencies as arguments—known as Dependency Injection—you decouple your logic from specific implementations. This makes your code more flexible and significantly easier to verify. Avoiding Magic Method Abuse Python provides immense power through dunder methods like `__new__` or `__getattr__`. While tempting, overriding these low-level hooks often leads to confusing code that behaves unpredictably. If you're using complex dunder logic to handle object creation, a Factory Pattern or a simple dictionary-based lookup is usually a more readable alternative. Clear, straightforward code always beats clever, cryptic implementation. By following these principles, you move away from the rigid "Java-style" OOP and toward a more flexible, Pythonic approach that emphasizes clarity and maintainability.
Jun 3, 2022Defining the Two Pillars of Design Many developers use encapsulation and information hiding interchangeably, but they serve distinct roles in software architecture. Encapsulation is the act of grouping related data and behaviors into a single unit, like a `Customer` class that contains names, IDs, and addresses. It creates a complete representation of an entity. Beyond grouping, it also establishes boundaries. These boundaries restrict how external code interacts with internal data, often using access modifiers like private or protected members. Information hiding is the strategic concealment of implementation details. It provides a "black box" interface, allowing other modules to interact with a component without knowing its internal mechanics. When you use a Stripe payment processor, your main application shouldn't care about specific API calls or data transformations happening under the hood. It only needs to know how to trigger a payment. Implementation and Access Control In Python, encapsulation is often signaled through naming conventions. While the language doesn't strictly enforce access restrictions like Java, developers use single or double underscores to indicate intent. ```python class Order: def __init__(self): # Protected member: a boundary for encapsulation self._payment_status = "PENDING" def pay(self): # Information hiding: user doesn't see the internal logic self._payment_status = "PAID" def is_paid(self) -> bool: return self._payment_status == "PAID" ``` In this `Order` class, `_payment_status` is protected. By providing methods like `is_paid()`, we hide the internal representation. If we later change the status from a string to an integer, external code remains untouched because it relies on the method, not the variable. Impact on Cohesion and Coupling These concepts directly influence the health of your codebase. Encapsulation increases **cohesion** by ensuring that a class does exactly what it's supposed to do and nothing more. Information hiding reduces **coupling** by removing dependencies between different parts of the system. High cohesion and low coupling make your software easier to maintain, test, and scale over time. Syntax Notes and Best Practices Python uses the `_` prefix for protected members and `__` for private members (which triggers name mangling). Always prefer high-level methods over direct variable access to maintain the integrity of your information hiding strategy. Use Enums to represent states clearly without exposing raw strings or integers to the end-user.
Jan 14, 2022Overview of Structural Pattern Matching Structural Pattern Matching represents a major evolution in how Python handles conditional logic. While it shares a superficial resemblance to the `switch-case` statements found in C or Java, it offers far more than simple value comparisons. This feature allows you to match complex data structures, extract nested values, and apply conditional logic directly within a case. It streamlines code that would otherwise require nested `if-elif-else` blocks, making it both more readable and less prone to errors when dealing with varied input formats. Prerequisites To follow this guide, you should have a solid grasp of Python fundamentals, including functions, lists, and basic object-oriented programming. Most importantly, you must use **Python 3.10** or newer, as the `match` and `case` keywords were not available in previous versions. Key Libraries & Tools - **shlex**: A standard library module used for splitting strings into tokens, specifically designed for command-line syntax parsing. - **dataclasses**: Used to create concise data-holding classes that work seamlessly with pattern matching. - **pyenv**: A tool for managing multiple Python versions, useful for testing newer features like 3.10. Code Walkthrough: Parsing Commands Let's look at how we can use matching to build a robust command-line interface. First, we'll implement a sophisticated match that handles multiple keywords and variable arguments. ```python import shlex def run_command(command_str): # Split input while respecting quotes split_cmd = shlex.split(command_str) match split_cmd: case ["quit" | "exit" | "bye", *rest] if "--force" in rest: print("Force quitting...") return False case ["quit" | "exit" | "bye", *rest]: print("Quitting normally.") return False case ["load", filename]: print(f"Loading: {filename}") case _: print(f"Unknown command: {command_str}") return True ``` In this snippet, we use the `|` (OR) operator to match multiple exit commands in a single line. The `*rest` syntax captures any additional arguments into a list. Notice the **guard condition** (`if "--force" in rest`), which allows us to filter the match based on external logic. Pattern Matching with Objects One of the most powerful aspects is matching against class attributes. By using dataclasses, we can match specific object structures. ```python from dataclasses import dataclass @dataclass class Command: action: str args: list def process_obj(cmd: Command): match cmd: case Command(action="load", args=[filename]): print(f"Object match: Loading {filename}") case Command(action="quit", args=["--force", *_]): print("Object match: Force quit detected.") ``` This syntax verifies that the object is an instance of `Command` and checks specific attribute values simultaneously. It is significantly cleaner than multiple `isinstance` and `getattr` calls. Syntax Notes - **The Underscore (_)**: This acts as a wildcard, matching anything without binding it to a variable. It is standard for the default case. - **Binding Variables**: When you use a name like `filename` in a case, Python automatically assigns the matched value to that variable for use inside the case block. Tips & Gotchas Order is critical. Python checks cases from top to bottom and stops at the first match. If you put a broad pattern (like `case [*rest]`) above a specific one (like `case ["load", name]`), the specific one will never execute. Always place your most specialized patterns at the top and your most generic catch-alls at the bottom.
Jul 9, 2021