<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[JPanganiban]]></title><description><![CDATA[Thoughts, published.]]></description><link>https://jpanganiban.com/</link><image><url>https://jpanganiban.com/favicon.png</url><title>JPanganiban</title><link>https://jpanganiban.com/</link></image><generator>Ghost 4.35</generator><lastBuildDate>Mon, 06 Apr 2026 20:53:02 GMT</lastBuildDate><atom:link href="https://jpanganiban.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Not All Feedback Is Created Equal: The Importance of Authentic User Feedback in Software Development]]></title><description><![CDATA[<blockquote>Software teams often charge into development with a clear vision, only to discover&#x2014;too late&#x2014;that the end product misses the mark for the people who actually use it.<br><br>In most software development projects&#x2014;f<em>eedback</em> is sought primarily from demos, stakeholders, or QA testers, rather than</blockquote>]]></description><link>https://jpanganiban.com/not-all-feedback-is-created-equal-the-importance-of-authentic-user-feedback-in-software-development/</link><guid isPermaLink="false">67b20819206068055da9b021</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Sun, 16 Feb 2025 16:10:25 GMT</pubDate><media:content url="https://jpanganiban.com/content/images/2025/02/andrew-seaman-4Fi_4Q6_eFM-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<blockquote>Software teams often charge into development with a clear vision, only to discover&#x2014;too late&#x2014;that the end product misses the mark for the people who actually use it.<br><br>In most software development projects&#x2014;f<em>eedback</em> is sought primarily from demos, stakeholders, or QA testers, rather than the real end users who rely on the software daily. While all of these perspectives have some value, not all feedback is created equal.</blockquote><h2 id="the-perils-of-%E2%80%9Cobservation-feedback%E2%80%9D">The Perils of &#x201C;Observation Feedback&#x201D;</h2><img src="https://jpanganiban.com/content/images/2025/02/andrew-seaman-4Fi_4Q6_eFM-unsplash.jpg" alt="Not All Feedback Is Created Equal: The Importance of Authentic User Feedback in Software Development"><p>Many enterprise teams rely on what you might call &#x201C;observation feedback&#x201D;&#x2014;comments gathered from product demonstrations or prototypes shown to managers, executives, or other stakeholders who don&#x2019;t actually use the software. This style of feedback:</p><ul><li>Is often based on a carefully choreographed demo that highlights a product&#x2019;s best features while glossing over potential pitfalls.</li><li>Can lead to overly positive or safe responses, since the audience is invested in the project&#x2019;s success but may not see everyday workflow challenges.</li><li>Lacks real-world context, making it easy to miss hidden usability issues or feature gaps that only emerge under genuine usage conditions.</li></ul><p>While it may be valuable for gauging stakeholder alignment, observation feedback doesn&#x2019;t reveal how well the product meets real users&#x2019; needs.</p><h2 id="why-stakeholder-only-feedback-can-be-misleading">Why Stakeholder-Only Feedback Can Be Misleading</h2><p>Stakeholders&#x2014;such as senior management or non-technical clients&#x2014;often have a vested interest in the project but aren&#x2019;t necessarily the people clicking buttons day in and day out.</p><p>When teams rely primarily on stakeholder feedback:</p><ul><li>They risk building solutions that address high-level concerns rather than day-to-day user pain points.</li><li>They may overvalue &#x201C;nice-to-have&#x201D; features suggested by leadership while neglecting core functionality that actual users need.</li><li>They often get approval for the wrong product, leading to costly rewrites or user dissatisfaction once the software is released.</li></ul><h2 id="segue-the-role-of-qa-feedback">Segue: The Role of QA Feedback</h2><p>Quality Assurance (QA) teams are crucial to ensuring software meets certain performance, reliability, and security standards. However:</p><ul><li>QA feedback should be seen as &#x201C;quality feedback,&#x201D; not user feedback.</li><li>QA testers typically follow test plans focused on uncovering bugs or violations of specifications.</li><li>They do not always replicate the real-world flow of end users with varying tasks and objectives.</li></ul><p>While QA feedback is indispensable, it doesn&#x2019;t replace the experiential insight that only end users can provide.</p><h2 id="authentic-user-feedback-why-it-matters">Authentic User Feedback: Why It Matters</h2><p>Genuine user feedback comes from individuals who will <em>actually use</em> the software as part of their daily workflow. This feedback:</p><ul><li>Uncovers the true pain points and frustrations users encounter.</li><li>Guides meaningful refinements to functionality, design, and user experience.</li><li>Reduces the risk of costly post-release changes by surfacing issues early.</li></ul><p>By incorporating user feedback throughout the development lifecycle&#x2014;rather than waiting until late-stage demos&#x2014;teams can build a product that truly meets end-user needs from the get-go.</p><hr><h1 id="strategies-for-getting-the-right-feedback-at-the-right-time">Strategies for Getting the Right Feedback at the Right Time</h1><p>Building software that truly resonates with its users begins with forging a genuine connection between your development team and the people who will use your product every day.</p><p>Here are a few practical ways to make that happen:</p><h3 id="start-small-and-test-early">Start Small and Test Early</h3><p>Focus on delivering one truly valuable feature at a time, and get it into the hands of actual users as soon as you can. Observe how they interact with it in their daily workflow, then gather feedback to guide small, incremental improvements. It&#x2019;s far more effective to release a modest set of well-tested features and refine them in production than to wait until you&#x2019;ve built everything&#x2014;only to learn too late that certain parts don&#x2019;t align with real-world needs.</p><h3 id="get-users-to-actually-use-the-software">Get Users to Actually Use the Software</h3><p>Ultimately, the most authentic feedback flows from real-world usage. If your intended audience isn&#x2019;t using the software&#x2014;no matter how well-polished it appears&#x2014;it&#x2019;s a sign that something critical is missing. Perhaps it lacks essential features, the onboarding is too cumbersome, or it simply doesn&#x2019;t solve the problems users face. Whatever the reason, low adoption should sound the alarm that it&#x2019;s time to investigate and refine your approach.</p><h3 id="if-they%E2%80%99re-not-users-take-it-with-a-grain-of-salt">If They&#x2019;re Not Users, Take It with a Grain of Salt</h3><p>Feedback can come from a variety of sources&#x2014;stakeholders, managers, or even QA teams&#x2014;but if they aren&#x2019;t the ones using the software day in and day out, their insights only go so far. While their opinions can spark valuable discussions, they won&#x2019;t always highlight the real-world problems your users face. Treat this feedback as one piece of the puzzle, not the entire picture.</p><h3 id="observe-real-behavior">Observe Real Behavior</h3><p>Don&#x2019;t rely solely on bug reports or formal testing sessions. Sit side by side with actual users to watch them interact with your software in their natural environment. Notice where they hesitate, how they navigate between features, and which workarounds they invent. These unfiltered observations often uncover pain points that scripted tests or feedback forms can overlook.</p><h3 id="embrace-continuous-iteration">Embrace Continuous Iteration</h3><p>Once you&#x2019;ve gathered user insights, resist the urge to hold off on improvements until a &#x201C;big release.&#x201D; Instead, act on what you learn right away by rolling out small, incremental updates. This cycle of continuous iteration&#x2014;release, observe, refine&#x2014;keeps the product evolving in step with your users&#x2019; needs, rather than lagging behind or relying on outdated assumptions.</p><hr><p>Even the most carefully planned software can fall flat if it doesn&#x2019;t address real user needs. By gathering authentic user insights early and often&#x2014;rather than relying solely on demos, stakeholders, or QA teams&#x2014;you significantly reduce the risk of building features nobody wants or needs.</p><p>Think of each release as a stepping stone rather than a final destination. Through continuous iteration, data-driven metrics, and honest conversations with the people who matter most, your team can craft a product that truly delivers genuine value to its users.</p>]]></content:encoded></item><item><title><![CDATA[Greasing the Groove: Cultivating Excellence in Software Engineering]]></title><description><![CDATA[<p><em>To practice is to train: deliberately working on skills and techniques to improve proficiency and efficiency, much like an athlete honing their abilities through repetitive drills.</em></p><p><em>To practice is to do: it involves actively engaging in the tasks and challenges that are integral to the craft, applying knowledge in real-world</em></p>]]></description><link>https://jpanganiban.com/greasing-the-groove-cultivating-excellence-in-software-engineering/</link><guid isPermaLink="false">666add6b206068055da9afbc</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Thu, 13 Jun 2024 12:14:37 GMT</pubDate><media:content url="https://jpanganiban.com/content/images/2024/06/featured_hub81eea777ce867726156a5d0399caf38_33302_1320x0_resize_q75_box.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://jpanganiban.com/content/images/2024/06/featured_hub81eea777ce867726156a5d0399caf38_33302_1320x0_resize_q75_box.jpg" alt="Greasing the Groove: Cultivating Excellence in Software Engineering"><p><em>To practice is to train: deliberately working on skills and techniques to improve proficiency and efficiency, much like an athlete honing their abilities through repetitive drills.</em></p><p><em>To practice is to do: it involves actively engaging in the tasks and challenges that are integral to the craft, applying knowledge in real-world scenarios, and learning through hands-on experience.</em></p><hr><p>In strength training, there&#x2019;s a concept called &#x201C;greasing the groove&#x201D; (GtG) introduced by coach Pavel Tsatsouline. It involves practicing an exercise frequently without overexerting yourself, helping you get better without getting tired or burned out. This idea can also be applied to software engineering. By regularly practicing coding tasks in short, focused sessions, you can improve your skills and stay sharp without the stress and fatigue of long work hours.</p><p>Similarly, the Jesuit educational philosophy emphasizes repetition to help students deeply understand and remember what they learn. Greasing the groove in software engineering means consistently practicing and refining your skills. This approach helps you build and improve your abilities through regular, repetitive practice.</p><h2 id="a-missed-opportunity-in-career-growth">A missed opportunity in career growth</h2><p>The craft of programming is developed in two places: while at work and while off-work. Often, the latter is a missed opportunity. Many software engineers focus solely on their tasks during work hours, overlooking the potential for growth and skill enhancement during their personal time.</p><p>Greasing the groove means consistently practicing and refining your skills, both on and off the clock. It&#x2019;s about making small, regular efforts that contribute to long-term growth. By doing this, you can enhance your craft, stay up-to-date with the latest developments in tech, and maintain a healthy work-life balance without the risk of burnout.</p><hr><h2 id="putting-it-into-practice">Putting it into practice</h2><p>Understanding the theory behind greasing the groove is only the first step. To truly benefit from this approach, one must implement it in their daily routine.</p><p>This section outlines practical strategies and actionable steps to incorporate frequent, focused practice into your life, both at work and during personal time. By doing so, you can steadily improve your software engineering skills, enhance productivity, and achieve a higher level of mastery without the risk of burnout.</p><h3 id="practice-%E2%80%9Ccode-katas%E2%80%9D">Practice &#x201C;Code Katas&#x201D;</h3><p>Participate in coding exercises known as katas, where you solve small, well-defined problems repeatedly. These exercises are simple yet challenging, helping you refine your coding skills and deepen your understanding of fundamental programming concepts. Regularly working through katas hones your problem-solving abilities, reinforces good coding habits, and enhances your ability to think algorithmically. This consistent practice allows you to experiment with different approaches, write cleaner code, and become more adept at debugging, making it easier to apply these skills in real-world scenarios.</p><h3 id="build-a-project-sandbox">Build a project sandbox</h3><p>A project sandbox is like creating a &#x201C;spike solution&#x201D; in extreme programming&#x2014;a simplified version of your project where you can focus on the core architecture without getting bogged down by details. This method allows you to experiment with new ideas, test design patterns, and explore solutions in a low-risk environment. It&#x2019;s particularly useful for tackling complex problems or making significant changes, providing a safe space to innovate and refine your approach. This helps you understand the architecture better and identify potential issues early, leading to more robust and maintainable code in the final product.</p><h3 id="write-a-technical-blog-post"><strong>Write a technical blog post</strong></h3><p>Teaching others deepens your understanding. By writing about coding experiences, new technologies, or complex problems, you clarify your thoughts and solidify your knowledge. This process forces you to organize your ideas clearly, enhancing your grasp of the subject matter. Additionally, sharing your insights through blogs or documentation builds valuable resources for the coding community, fostering collaboration and benefiting both personal growth and the broader development community.</p><h3 id="join-a-coding-community"><strong>Join a coding community</strong></h3><p>Engage with online coding communities, participate in discussions, and help others with their coding problems. This interaction exposes you to diverse perspectives and solutions, enhancing your problem-solving skills. Additionally, being active in these communities allows you to receive feedback on your work and gain insights from more experienced developers, fostering continuous learning and improvement. This collaborative environment helps you become a more well-rounded and proficient software engineer.</p><hr><p>In my decade-and-a-half journey as a programmer, I&#x2019;ve experienced a lot. I&#x2019;ve joined hackathons, written code late into the night, learned new languages and frameworks, and faced burnout more than once. But I&#x2019;ve always managed to rekindle my passion for coding. It was only recently that I fully grasped this sustainable way to grow and improve as a programmer.</p><p>I write this to the engineers I work with. I hope you continuously grow in your journey as a programmer. By adopting the principles of greasing the groove, you can become more skilled and productive without overworking yourself. Embrace consistent, focused practice, and you&#x2019;ll find a sustainable path to mastering your craft.</p><hr><p>[0]: <a href="https://www.artofmanliness.com/health-fitness/fitness/get-stronger-by-greasing-the-groove/">https://www.artofmanliness.com/health-fitness/fitness/get-stronger-by-greasing-the-groove/</a><br>[1]: <a href="https://godinallthings.com/2013/05/20/repetition/">https://godinallthings.com/2013/05/20/repetition/</a><br>[2]: <a href="https://nankov.com/posts/code-katas-are-worth-doing/">https://nankov.com/posts/code-katas-are-worth-doing/</a><br>[3]: <a href="http://www.extremeprogramming.org/rules/spike.html">http://www.extremeprogramming.org/rules/spike.html</a></p>]]></content:encoded></item><item><title><![CDATA[Untangling the Django Model Monolith: A Guide to Extracting Concerns]]></title><description><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://jpanganiban.com/content/images/2023/09/photo-1501084291732-13b1ba8f0ebc.webp" class="kg-image" alt loading="lazy" width="2000" height="1337" srcset="https://jpanganiban.com/content/images/size/w600/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 600w, https://jpanganiban.com/content/images/size/w1000/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 1000w, https://jpanganiban.com/content/images/size/w1600/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 1600w, https://jpanganiban.com/content/images/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 2096w" sizes="(min-width: 720px) 720px"><figcaption>The iconic banyan tree in Pipiwai, Hawaii (Credits: Brandon Green from Unsplash)</figcaption></figure><h2 id="our-starting-point-the-overburdened-loan-model">Our Starting Point: The Overburdened Loan Model</h2><p>Imagine the <code>Loan</code> model as a bustling city center: it&apos;s crowded, chaotic, and handling way more than it was designed for. This model is no different. It acts like</p>]]></description><link>https://jpanganiban.com/untangling-the-django-model-monolith-a-guide-to-extracting-concerns/</link><guid isPermaLink="false">64fc2b76e7619c1ede3e01d1</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Sat, 09 Sep 2023 08:38:19 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://jpanganiban.com/content/images/2023/09/photo-1501084291732-13b1ba8f0ebc.webp" class="kg-image" alt loading="lazy" width="2000" height="1337" srcset="https://jpanganiban.com/content/images/size/w600/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 600w, https://jpanganiban.com/content/images/size/w1000/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 1000w, https://jpanganiban.com/content/images/size/w1600/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 1600w, https://jpanganiban.com/content/images/2023/09/photo-1501084291732-13b1ba8f0ebc.webp 2096w" sizes="(min-width: 720px) 720px"><figcaption>The iconic banyan tree in Pipiwai, Hawaii (Credits: Brandon Green from Unsplash)</figcaption></figure><h2 id="our-starting-point-the-overburdened-loan-model">Our Starting Point: The Overburdened Loan Model</h2><p>Imagine the <code>Loan</code> model as a bustling city center: it&apos;s crowded, chaotic, and handling way more than it was designed for. This model is no different. It acts like a monolithic structure, struggling under the weight of its multiple responsibilities. Take a look:</p><pre><code class="language-python">class Loan(models.Model):
    borrower = ForeignKey(Borrower)
    beneficiary = ForeignKey(Student)
    guarantor = ForeignKey(Guarantor)
    principal = MoneyField()
    loan_product = ForeignKey(LoanProuduct)
    
    # Underwriting functions
    def underwrite(self): ...

    # Documents to be generated
    def generate_contract(self): ...
    def generate_disclosure_statement(self): ...

    # Monitoring functions
    def get_dpd_status(self): ...
    def get_behavioral_score(self): ...
    
    # Email notification functions
    def send_repayment_reminder(self): ...
    def send_payment_acknowledgement(self): ...</code></pre><p>The code above is merely an outline, a folded version of what is a behemoth in reality&#x2014;imagine that each method expands to dozens or even hundreds of lines of code. Collectively, we&apos;re talking about a file that can easily exceed 2,000 lines.</p><p>The model is responsible for underwriting loans, generating various types of documents, monitoring loan performance, and even managing email notifications. This amalgamation of tasks makes it difficult to manage, and its extensive list of dependencies can be paralyzing.</p><p>In essence, our <code>Loan</code> model is like a jack-of-all-trades but a master of none. It&apos;s cumbersome to navigate, prone to errors, and a real challenge for any developer to work with. We need a strategy to disentangle this Gordian knot of code.</p><h2 id="step-by-step-extracting-the-document-generation-concerns">Step-by-Step: Extracting the Document Generation Concerns</h2><p>We can see a trend in our model. It is concerned with three categories of behaviors: underwriting, document generation, and monitoring. Let&apos;s extract the concern of document generation:</p><h3 id="step-1-identify-methods-for-extraction">Step 1: Identify Methods for Extraction</h3><p>First, pinpoint the methods within your model related to document generation. In the case of the <code>Loan</code> model, these methods could be <code>generate_contract</code> and <code>generate_disclosure_statement</code>.</p><h3 id="step-2-create-the-docgen-base-class">Step 2: Create the Docgen Base Class</h3><p>Create a new file, <code>app/</code><a href="http://docgens.py">docgens.py</a>, and within it, define the <code>Docgen</code> class. This will act as the base class for all your document generators.</p><pre><code class="language-python"># app/docgens.py
class Docgen(object):
    template_name = None

    def __init__(self, subject):
        self.subject = subject

    def get_template_name(self):
        return self.template_name

    def get_context(self):
        return {}

    def generate_file(self):
        # File generation logic
        pass</code></pre><h3 id="step-3-subclass-for-specific-documents">Step 3: Subclass for Specific Documents</h3><p>Create specific subclasses for each document you wish to generate. In your case, you&apos;ll want a <code>LoanContractDocgen</code> and a <code>LoanDisclosureDocgen</code>.</p><pre><code class="language-python"># loans/docgens.py
from app.docgens import Docgen

class LoanContractDocgen(Docgen):
    template_name = &quot;contracts/loan_contract.html&quot;

    def get_context(self):
        # Build context specific to Loan Contract
        pass

class LoanDisclosureDocgen(Docgen):
    template_name = &quot;contracts/loan_disclosure.html&quot;</code></pre><h3 id="step-4-update-views">Step 4: Update Views</h3><p>Update your views to utilize the new <code>Docgen</code> concern. Remove the old methods from the <code>Loan</code> model, and update your view functions.</p><pre><code class="language-python"># loans/views.py
from .docgens import LoanContractDocgen

def generate_contract(request, loan_id):
    loan = get_object_or_404(Loan, id=loan_id)
    loan_contract = LoanContractDocgen(loan)
    loan.contract_file.set_file(loan_contract.generate_file())
    loan.save()</code></pre><h3 id="step-5-move-dependencies">Step 5: Move Dependencies</h3><p>Move any dependencies that are only required for document generation into <code>app/</code><a href="http://docgens.py">docgens.py</a>. This helps isolate dependencies related to each concern.</p><p>We still have a few concerns left inside our <code>Loan</code> model, and it&apos;s now your turn to extract them. Let me give you some guiding principles to help you proceed.</p><h2 id="extracting-model-concerns-guiding-principles">Extracting Model Concerns: Guiding Principles</h2><h3 id="guiding-principle-1-models-are-stable-building-blocks">Guiding Principle #1: Models are Stable Building Blocks</h3><p>Rather than feeding individual attributes to the concern, just pass the entire model instance. Attributes may change over time, but the model itself is a stable interface.</p><pre><code class="language-python"># Instead of this
docgen = LoanContractDocgen(loan.principal, loan.borrower, loan.guarantor)

# Do this
docgen = LoanContractDocgen(loan)</code></pre><h3 id="guiding-principle-2-models-are-the-core-subject">Guiding Principle #2: Models are the Core Subject</h3><p>Concerns operate on data, and the most coherent place to pull this data from is the model itself. Think of concerns as machines that use the model as an input.</p><pre><code class="language-python"># Monitoring concern that uses Loan model as its subject
monitor = LoanMonitor(loan)
dpd_status = monitor.get_dpd_status()</code></pre><h3 id="guiding-principle-3-keep-concerns-out-of-models">Guiding Principle #3: Keep Concerns Out of Models</h3><p>Once you&apos;ve removed a concern from a model, don&apos;t put it back. Use the concerns where they are needed.</p><pre><code class="language-python"># Don&apos;t do this
class Loan(models.Model):
    def underwrite(self):
        underwriter = Underwriter(self)
        underwriter.underwrite()

# Do this
def process_underwriting(request, loan_id):
    loan = get_object_or_404(Loan, id=loan_id)
    underwriter = Underwriter(loan)
    underwriter.underwrite()</code></pre><h3 id="guiding-principle-4-concerns-are-not-a-novelty">Guiding Principle #4: Concerns are Not a Novelty</h3><p>The concept of extracting concerns is nothing new. It aligns with established patterns like forms, serializers, and querysets.</p><pre><code class="language-python"># serializers.py - Concern for serialization
class LoanSerializer(serializers.ModelSerializer):
    class Meta:
        model = Loan
        fields = &apos;__all__&apos;

# forms.py - Concern for form handling
class LoanForm(forms.ModelForm):
    class Meta:
        model = Loan
        fields = &apos;__all__&apos;</code></pre><h3 id="guiding-principle-5-encapsulation-over-exposure">Guiding Principle #5: Encapsulation Over Exposure</h3><p>Prefer to encapsulate logic within concerns, rather than exposing model attributes and methods. This way, you don&apos;t accidentally manipulate model data in ways that are inconsistent with the business logic.</p><pre><code class="language-python"># Instead of directly modifying model attributes
loan.status = &quot;Approved&quot;

# Use a concern to encapsulate this logic
underwriter = Underwriter(loan)
underwriter.approve()</code></pre><h3 id="guiding-principle-6-make-concerns-stateless">Guiding Principle #6: Make Concerns Stateless</h3><p>Concerns should be stateless, meaning they should not maintain state between function calls. This makes the code easier to reason about and test.</p><pre><code class="language-python"># Don&apos;t store state in the concern
class BadUnderwriter(object):
    def __init__(self, loan):
        self.loan = loan
        self.is_approved = False  # Stateful

# Do make it stateless
class GoodUnderwriter(object):
    def __init__(self, loan):
        self.loan = loan
    
    def approve(self):
        self.loan.status = &quot;Approved&quot;</code></pre><h3 id="guiding-principle-7-use-composition-over-inheritance">Guiding Principle #7: Use Composition Over Inheritance</h3><p>When you need to share behavior across different concerns, use composition rather than inheritance. This will allow you to mix and match functionalities more freely.</p><pre><code class="language-python">class DisclosureWithSignature(LoanDisclosureDocgen):
    def get_context(self):
        context = super().get_context()
        context[&apos;signature&apos;] = SignatureComponent().get_signature(self.loan)
        return context</code></pre><hr><p>By incorporating these guiding principles into your development practices, not only do you enhance the maintainability and reliability of your codebase, but you also pave the way for a more sustainable and scalable software architecture.</p><p>Django&apos;s framework is already well-architected and highly extensible, but when you integrate the concept of concerns, you add another layer of scalability and extensibility. You&apos;ll find that navigating and extending your code becomes a more manageable endeavor, and new team members can more easily understand the architecture</p>]]></content:encoded></item><item><title><![CDATA[How to Write Good Code Abstractions—The Art of Trimming Code]]></title><description><![CDATA[<p>One of the things I enjoy in writing code is writing code abstractions. When implemented well, they result in clean code. Readable. Maintainable. Less &quot;code dread&quot;, more &quot;joy in creating&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://jpanganiban.com/content/images/2023/03/image.png" class="kg-image" alt loading="lazy" width="700" height="525" srcset="https://jpanganiban.com/content/images/size/w600/2023/03/image.png 600w, https://jpanganiban.com/content/images/2023/03/image.png 700w"><figcaption>A Japanese maple bonsai. Credits to <a href="https://balconygardenweb.com/best-trees-for-bonsai-best-bonsai-plants/">Balcony Web Garden</a>&#xA0;</figcaption></figure><p>I haven&apos;t tended for</p>]]></description><link>https://jpanganiban.com/how-to-write-good-code-abstractions/</link><guid isPermaLink="false">6402d0ffe7619c1ede3dff0b</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Sat, 04 Mar 2023 06:21:51 GMT</pubDate><content:encoded><![CDATA[<p>One of the things I enjoy in writing code is writing code abstractions. When implemented well, they result in clean code. Readable. Maintainable. Less &quot;code dread&quot;, more &quot;joy in creating&quot;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://jpanganiban.com/content/images/2023/03/image.png" class="kg-image" alt loading="lazy" width="700" height="525" srcset="https://jpanganiban.com/content/images/size/w600/2023/03/image.png 600w, https://jpanganiban.com/content/images/2023/03/image.png 700w"><figcaption>A Japanese maple bonsai. Credits to <a href="https://balconygardenweb.com/best-trees-for-bonsai-best-bonsai-plants/">Balcony Web Garden</a>&#xA0;</figcaption></figure><p>I haven&apos;t tended for a bonsai tree&#x2014;but I can imagine writing abstractions to be like it. Trim until they&apos;re pleasing to the eyes. Remove the unwanted parts. Grow it to elegance.</p><p>In this post, I&apos;ll share some properties of code abstractions I found to be pleasing to the eyes.</p><h2 id="reflect-the-abstract-into-concrete">Reflect the abstract into concrete</h2><pre><code class="language-python">class LendingContract(models.Model)
class LendingContractDocGen(PDFDocGen)

class Investor(models.Model)
class InvestorContractDocGen(PDFDocGen)

class InvestorDeposit(models.Model)
class InvestorWithdraw(models.Model)</code></pre><p>Without seeing the implementation details, we can see that we&apos;re working with &quot;entities&quot;. These entities, while each have their own responsibilities, all work together. Sub-systems that create a large system.</p><p>A <code>LendingContract</code> stored in the database abstracts an actual legal agreement in the real world. An <code>Investor</code> stored in the database holds the record of an individual involved in the business.</p><p>Entities are swappable. Good structures allow that.</p><pre><code class="language-python">class LendingContractDocGenV2(PDFDocGen):
	template = &quot;contracts/lending_contract_v2.html&quot;
    
# lenders/views.py
def generate_contract_file(request, id):
    ...
    # Replaced the contract used in the controller
    contract.file = LendingContractDocGenV2(contract).\
    get_encrypted_file(contract.passphrase)</code></pre><p>In the example above, I declared <code>LendingContractDocGenV2</code> then swapped out the previously set <code>LendingContractDocGen</code> with it.</p><p>You could even take it up a notch by making the <code>generate_contract_file()</code> class-based and setting the DocGen as a class-variable:</p><pre><code class="language-python">class GenerateContractView(View):
    # swap LendingContractDocGenV2 here
    contract_docgen = LendingContractDocGen
 
    def post(self, request):
        contract = LendingContract.objects.get(id=id)
        contract.file = self.contract_docgen(contract)\
            .get_encrypted_file(contract.passphrase)
        contract.file.save()
        contract.save()</code></pre><h2 id="hide-away-sub-system-implementation-details">Hide away sub-system implementation details</h2><pre><code class="language-python"># app/docgen.py

import pdfkit
from django.template.loader import render_to_string

class PDFDocGen(object):
	template = &quot;docgens/default_template.html&quot;
    context = {}
    
    def __init__(self, subject):
    	self.subject = subject
        
    def get_template(self):
    	return self.template
            
    def get_context_data(self):
    	return context

    # XXX: Complex bits start here
    def get_file(self):
        context = self.get_context()
        template = self.get_template()

        html_contents = render_to_string(template, context)
        pdf_contents = pdfkit.from_string(
            input=html_contents,
            output_path=None,
            options=self.get_layout_settings()
        )
        return io.BytesIO(pdf_contents)

    def get_encrypted_file(self, passphrase):
        from PyPDF2 import PdfFileReader, PdfFileWriter

        file = self.get_file()
        input_pdf = PdfFileReader(file)
        output_pdf = PdfFileWriter()
        output_pdf.append_pages_from_reader(input_pdf)
        output_pdf.encrypt(passphrase)
        with io.BytesIO() as output_stream:
            output_pdf.write(output_stream)
            output_stream.seek(0)
            return output_stream.read()</code></pre><p>Sub-system complexity should be hidden away from the system implementation.</p><p>In the code example above, document generation <code>PDFDocGen</code> is a sub-system that has a complex function that produces a pdf file from a template <code>PDFDocGen.get_file()</code> and another that produces an encrypted one <code>PDFDocGen.get_encrypted_file(passphrase)</code>. This is irrelevant to the lending system we&apos;re building. We know we want to create encrypted PDF files, but the algorithm is irrelevant. We might need to reuse the sub-system in other areas of the system also (lender contracts, bond contracts, non-disclosure agreements, etc) As such, this detail should be abstracted.</p><p>One would write a utility function, then create partial functions for reusability. It would look like this:</p><pre><code class="language-python"># apps/utils.py
def generate_pdf_file(template, filename, context):
    # Do complex bits here...
    
# lending/utils.py
lending_contract_pdf_file = partial(
    generate_pdf_file,
    &quot;lending_contract.pdf&quot;,
    &quot;contracts/lending_contract.html&quot;
)</code></pre><p>While it works&#x2014;reusable and extensible, I found it less beautiful to this alternative:</p><pre><code class="language-python"># lending/docgen.py
class LendingContractDocGen(PDFDocGen):
    template = &quot;contracts/lending_contract.html&quot;
    filename = &quot;lending_contract.pdf&quot;
    
    def get_context_data(self):
        return {
            &apos;signer_name&apos;: self.subject.signer_name,
        }
        
# investors/docgen.py
class InvestorContractDocGen(PDFDocGen):
    template = &quot;contracts/investor_contract.html&quot;
    filename = &quot;investor_contract.pdf&quot;
    
    def get_context_data(self):
        return {
            &apos;signer_name&apos;: self.subject.signer_name,
        }</code></pre><h2 id="make-system-implementation-behavior-declarative">Make system implementation behavior declarative</h2><pre><code class="language-python"># lending/models.py
class LendingContract(models.Model):
    signer_name = models.CharField(max_length=255)
    passphrase = models.CharField(max_length=255)
    file = models.FileField()
    
# lending/docgen.py
class LendingContractDocGen(PDFDocGen):
    template = &quot;contracts/lending_contract.html&quot;
    filename = &quot;lending_contract.pdf&quot;
    
    def get_context_data(self):
        return {
            &apos;signer_name&apos;: self.subject.signer_name,
        }

# lending/views.py
def generate_contract_file(request, id):
    contract = LendingContract.objects.get(id=id)
    contract.file = LendingContractDocGen(contract)\
        .get_encrypted_file(contract.passphrase)
    contract.file.save()
    contract.save()</code></pre><p>Good abstractions encourage you to write system behavior more declaratively than imperatively. While there would still be instances of imperative code, it would be comprehensible that it explains how &quot;entities&quot; interact with each other.</p><p>The example above ties how the abstraction sits in between the Model and the Controller (or called &quot;views.py&quot; in Django-land).</p><p>The model <code>LendingContract</code> declares the fields it has and how it maps to the database table.</p><p>The docgen <code>LendingContractDocGen</code> declares with the template details and how overrides how it generates the context loaded into the template.</p><p>Finally, the <code>generate_contract_file()</code> controller &#xA0;&quot;controls the flow&quot; of the interaction between these two objects. When it is called, it does exactly what it&apos;s supposed to do&#x2014;it generates the lending contract and saves it into the <code>LendingContract</code> table.</p><p>System control flow should always be in controllers.</p><hr><p>I hope you found value in this post! Post in the comments section below how you write trim your code.</p><p>While I was only able to cover one pattern (<a href="https://refactoring.guru/design-patterns/prototype">Prototype pattern</a>) in the examples above, there&apos;s a lot more you could apply in your practice&#x2014;you can find them here: <a href="https://refactoring.guru/">Refactoring Guru</a>. It distills all the patterns described in the book written by the Gang of Four (look it up).</p>]]></content:encoded></item><item><title><![CDATA[Two of the Many Distribution Mediums for Digital Content]]></title><description><![CDATA[<p>There are many ways to distribute digital content, but right now, I want to talk about two: Web and Mobile.</p><h2 id="web-on-expansion">Web on expansion</h2><p>Web is both public and private: Some content is indexable by crawlers while some are behind auth walls. Everyone who has access to the internet has access</p>]]></description><link>https://jpanganiban.com/two-of-the-many/</link><guid isPermaLink="false">624cc478e7619c1ede3dfe7d</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Tue, 05 Apr 2022 22:50:51 GMT</pubDate><content:encoded><![CDATA[<p>There are many ways to distribute digital content, but right now, I want to talk about two: Web and Mobile.</p><h2 id="web-on-expansion">Web on expansion</h2><p>Web is both public and private: Some content is indexable by crawlers while some are behind auth walls. Everyone who has access to the internet has access to the web (accessible). While it has a lower hurdle for acquisition, it does not provide the same level of capture as a mobile app would have.</p><figure class="kg-card kg-image-card"><img src="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406061936.png" class="kg-image" alt loading="lazy" width="570" height="367"></figure><p>&quot;Remember icanhascheeseburger? What was the URL again?&quot; -- It&apos;s easy to forget URLs of websites you don&apos;t visit often. Hopefully you can Google it again (importance of SEO).</p><figure class="kg-card kg-image-card"><img src="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406060852.png" class="kg-image" alt loading="lazy" width="600" height="445" srcset="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406060852.png 600w"></figure><p>Building on the web is great for growing your top of the funnel, from Awareness to Conversion. By making your content public, you reach a wider audience -- even if it is not something that they don&apos;t need now, it might be something that they would need in the future. You are building Mindshare.</p><figure class="kg-card kg-image-card"><img src="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406061358.png" class="kg-image" alt loading="lazy" width="245" height="200"></figure><h2 id="mobile-on-retention">Mobile on retention</h2><p>It provides you a deeper capture on the users for as long as they have the app. You get to nudge (in-app notifications) them at almost no cost. However, it has a taller hurdle for acquisition, whatever is in the app must be valuable enough for the user to install the app (commitment); and valuable enough for the user to keep it (personal data).</p><figure class="kg-card kg-image-card"><img src="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406061656.png" class="kg-image" alt loading="lazy" width="500" height="375"></figure><p>There&apos;s a stigma on social apps where they access your data and sell it to others. It&apos;s at the back of all of our heads. But we install it anyway, because we want/need that app.</p><figure class="kg-card kg-image-card"><img src="https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406062446.png" class="kg-image" alt loading="lazy" width="940" height="788" srcset="https://jpanganiban.com/content/images/size/w600/2022/04/Pasted-image-20220406062446.png 600w, https://jpanganiban.com/content/images/2022/04/Pasted-image-20220406062446.png 940w" sizes="(min-width: 720px) 720px"></figure><p>Getting the user to install the app is the first step (warm lead). It signals intent that they want to use/transact-with your app. The bottom portion of the marketing funnel, Transaction to Loyalty, takes play.</p><h2 id="both-distribution-channels-can-benefit-from-content-play">Both distribution channels can benefit from content play</h2><p>For mobile/private-web, we can draw inspiration from Patreon (user-generated/various), Masterclass (published/educational), Netflix (published), Spotify (music/vetted), Headspace (in-house/utility).</p><p>For public/semi-public web, we can draw inspiration from Medium (user-generated/various), NYTimes (published/news), Youtube (video/user-generated), Head-Fi (user-generated/niche forum).</p><hr><h2 id="great-if-you-can-build-both-at-once-but">Great if you can build both at once, but...</h2><p>Sometimes, resources are constrained and you can only build one first. Pick one goal (expansion or retention) then run with it.</p>]]></content:encoded></item><item><title><![CDATA[New Blog: First Post]]></title><description><![CDATA[<p>This is my first post on my new blog. After seeing how the new ghost interface looked like, I knew I had to set it up. Now that it&apos;s set up, it&apos;s time to write on it.</p><p>I&apos;ll be writing life, software engineering, and</p>]]></description><link>https://jpanganiban.com/first-post/</link><guid isPermaLink="false">6201c5dae7619c1ede3dfbe2</guid><dc:creator><![CDATA[Jesse Panganiban]]></dc:creator><pubDate>Tue, 08 Feb 2022 01:34:42 GMT</pubDate><content:encoded><![CDATA[<p>This is my first post on my new blog. After seeing how the new ghost interface looked like, I knew I had to set it up. Now that it&apos;s set up, it&apos;s time to write on it.</p><p>I&apos;ll be writing life, software engineering, and other things of interest. Stay tuned!</p>]]></content:encoded></item></channel></rss>