{"id":95,"date":"2025-11-20T20:28:00","date_gmt":"2025-11-20T20:28:00","guid":{"rendered":"https:\/\/wordpress.joeltan.me\/?p=95"},"modified":"2026-01-26T11:37:55","modified_gmt":"2026-01-26T11:37:55","slug":"why-my-next-small-script-starts-with-objects-not-functions","status":"publish","type":"post","link":"https:\/\/joeltan.me\/?p=95","title":{"rendered":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions"},"content":{"rendered":"\n<p>It always starts the same way.<\/p>\n\n\n\n<p>You just need a <strong>small script<\/strong>.<\/p>\n\n\n\n<p>In my case, it was \u201cjust\u201d a helper to get a Salesforce data migration over the line. I needed to switch off flows and validation rules, loosen some picklists, load data, then switch everything back on.<\/p>\n\n\n\n<p>So I did what most of us do: I opened a file and started writing functions.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>fetch_active_flows(object_id)<\/code><\/li>\n\n\n\n<li><code>deactivate_flow_trigger(object_id)<\/code><\/li>\n\n\n\n<li><code>activate_flow_trigger(object_id)<\/code><\/li>\n\n\n\n<li>\u201cJust one more helper\u201d to glue it together<\/li>\n<\/ul>\n\n\n\n<p>The script worked. It passed a quick test. I moved on.<\/p>\n\n\n\n<p>Weeks later, I had to run another migration, with more objects and more \u201cthings\u201d to toggle. That\u2019s when the cracks showed. The script was no longer \u201csmall\u201d \u2014 and it definitely wasn\u2019t easy to change safely.<\/p>\n\n\n\n<p>That experience is why my next \u201csmall script\u201d now <strong>starts with objects, not functions<\/strong>.<\/p>\n\n\n\n<p>This post walks through that shift using real code: how I refactored a procedural Salesforce migration helper into an object\u2011oriented design built around <strong>House, Room, and Switch<\/strong> \u2014 and why that\u2019s more than just clever naming.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The First Version: A Script That Grew Fangs<\/h2>\n\n\n\n<p>My original approach in <code>oldflow.py<\/code> was purely procedural. For example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\ndef deactivate_flow_trigger(object_id):\n  &quot;&quot;&quot;\n  Deactivate all active flow triggers related to the object\n  &quot;&quot;&quot;\n  cfv = fetch_active(object_id)\n  for record in cfv&#x5B;&#039;records&#039;]:\n\n    flow_id = record&#x5B;&#039;Id&#039;]\n    latest_version_id = record&#x5B;&#039;LatestVersionId&#039;]\n    api_name = record&#x5B;&#039;ApiName&#039;]\n    is_active = record&#x5B;&#039;IsActive&#039;]\n\n    print(f&quot;Updating...Flow ID: {flow_id}, Latest Version ID: {latest_version_id}, API Name: {api_name}, IsActive: {is_active}&quot;)\n    update_flowdefinition(latest_version_id,0)\n\ndef activate_flow_trigger(object_id):\n  &quot;&quot;&quot;\n  Activate all flow triggers related to the object from the baseline that should be active\n  &quot;&quot;&quot;\n  should_active = fetch_should_active(object_id)\n  curr_inactive = fetch_inactive(object_id)\n  for record in curr_inactive&#x5B;&#039;records&#039;]:\n\n    flow_id = record&#x5B;&#039;Id&#039;]\n    latest_version_id = record&#x5B;&#039;LatestVersionId&#039;]\n    api_name = record&#x5B;&#039;ApiName&#039;]\n    is_active = record&#x5B;&#039;IsActive&#039;]\n    curr_version_number = record&#x5B;&#039;VersionNumber&#039;]\n\n    print(f&quot;Looping Inactive Flow ID: {flow_id}, Latest Version ID: {latest_version_id}, API Name: {api_name}, IsActive: {is_active}, Current Vn:{curr_version_number}&quot;)\n\n    for lkp in should_active:\n        if lkp&#x5B;&#039;Id&#039;] == flow_id:\n            update_flowdefinition(latest_version_id, curr_version_number)\n<\/pre><\/div>\n\n\n<p>There\u2019s nothing \u201cwrong\u201d with this in isolation. It:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fetches active flows.<\/li>\n\n\n\n<li>Deactivates them before migration.<\/li>\n\n\n\n<li>Later, looks at a baseline and reactivates the right ones.<\/li>\n<\/ul>\n\n\n\n<p>The problem wasn\u2019t correctness; it was <strong>shape<\/strong>. Every time I added a new type of thing to control (validation rules, picklists), I ended up copying the same pattern:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fetch stuff.<\/li>\n\n\n\n<li>Loop over records.<\/li>\n\n\n\n<li>Do some CLI magic.<\/li>\n\n\n\n<li>Hope I didn\u2019t miss anything.<\/li>\n<\/ul>\n\n\n\n<p>The more functions I added, the harder it was to see the higher\u2011level intent:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u201cFor this object, switch off everything that could interfere, migrate, then restore exactly what should be on.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>That\u2019s when I stopped and asked: \u201cIf I rewrote this from scratch, pretending it\u2019s not \u2018just\u2019 a script, what would it look like?\u201d<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Mental Shift: Think in Objects, Not Steps<\/h2>\n\n\n\n<p>When I described the process out loud, it sounded nothing like my code:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u201cEach Salesforce object is like a <strong>house<\/strong>.\u201d<\/li>\n\n\n\n<li>\u201cFlows, validation rules, picklists \u2014 they\u2019re <strong>rooms<\/strong> in that house.\u201d<\/li>\n\n\n\n<li>\u201cEach individual flow or rule is a <strong>switch<\/strong> I can turn off and on.\u201d<\/li>\n<\/ul>\n\n\n\n<p>The more I leaned into that image, the clearer the structure became:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A <strong>Switch<\/strong> represents anything that can be turned on\/off (flow, rule, etc.).<\/li>\n\n\n\n<li>A <strong>Room<\/strong> manages a set of switches for a given object.<\/li>\n\n\n\n<li>A <strong>House<\/strong> orchestrates rooms for that object.<\/li>\n<\/ul>\n\n\n\n<p>So I rewrote the script around those objects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The New Shape: House, Rooms, Switches<\/h2>\n\n\n\n<p>Here\u2019s the high\u2011level orchestration now:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom data import DataService\nfrom flow import FlowRoom\nfrom validationrule import ValidationRuleRoom\nfrom picklist.picklist import PicklistRoom\n\nclass House():\n    def __init__(self, name):\n        self.Name = name\n        self.Id = self.get_object_id()\n        self.Rooms = self.create_rooms()\n\n    def get_object_id(self):\n        return DataService.get_object(self.Name)\n\n    def create_rooms(self):\n        rooms_dict = {}\n        flow_room = FlowRoom(self.Id)\n        rooms_dict&#x5B;&quot;Flow&quot;] = flow_room\n        validation_rule_room = ValidationRuleRoom(self.Id)\n        rooms_dict&#x5B;&quot;ValidationRule&quot;] = validation_rule_room\n        picklist_room = PicklistRoom(self.Name) \n        rooms_dict&#x5B;&quot;Picklist&quot;] = picklist_room\n        return rooms_dict\n\n    def visit(self):\n        sorted_keys = sorted(self.Rooms.keys())\n        for key in sorted_keys:\n            room = self.Rooms&#x5B;key]\n            room.enter()\n\n    def depart(self):\n        sorted_keys = sorted(self.Rooms.keys())\n        for key in sorted_keys:\n            room = self.Rooms&#x5B;key]\n            room.exit()\n<\/pre><\/div>\n\n\n<p>And the migration flow becomes:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nif __name__ == &quot;__main__&quot;:\n    house = House(&quot;Account&quot;)\n\n    # 1. Before migration: turn off flows, rules, picklists\n    house.visit()\n\n    # ... run migration ...\n\n    # 2. After migration: restore everything\n    house.depart()\n<\/pre><\/div>\n\n\n<p>Same job as before \u2014 but now the top\u2011level code is telling the story directly, not drowning in detail.<\/p>\n\n\n\n<p>The power comes from the OO concepts under the hood: <strong>abstraction, encapsulation, inheritance, polymorphism<\/strong>. Here\u2019s how they show up in this very concrete problem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Abstraction: Separating \u201cWhat\u201d From \u201cHow\u201d<\/h2>\n\n\n\n<p><strong>Abstraction<\/strong> is about describing what you can do, without exposing how it\u2019s done.<\/p>\n\n\n\n<p>At the heart of this design are two abstract base classes:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom abc import ABC, abstractmethod\n\nclass Room(ABC):\n    def __init__(self, obj_id):\n        self.obj_id = obj_id\n\n    @abstractmethod\n    def get_switches():\n        return &#x5B;]\n\n    @abstractmethod\n    def enter():\n        pass\n\n    @abstractmethod\n    def exit():\n        pass\n\nclass Switch(ABC):\n    def __init__(self, Id, Name, InitialState):\n        self.Id = Id\n        self.Name = Name\n        self.InitialState = InitialState\n\n    @abstractmethod\n    def on(self):\n        pass\n\n    @abstractmethod\n    def off(self):\n        pass\n<\/pre><\/div>\n\n\n<p>The abstractions here are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A <code>Switch<\/code> is \u201csomething that can be turned on or off.\u201d<\/li>\n\n\n\n<li>A <code>Room<\/code> is \u201csomething that can be entered (prepare) and exited (restore) for a given object.\u201d<\/li>\n<\/ul>\n\n\n\n<p>The main script doesn\u2019t need to know:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>How flows are actually deactivated.<\/li>\n\n\n\n<li>How validation rules are disabled.<\/li>\n\n\n\n<li>How picklists are modified.<\/li>\n<\/ul>\n\n\n\n<p>It only needs to know:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nroom.enter()  # get ready (e.g., turn things off)\nroom.exit()   # put it back (e.g., turn things on)\n<\/pre><\/div>\n\n\n<p>That\u2019s abstraction: the calling code deals in <strong>concepts<\/strong>, not implementation details.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Encapsulation: Keeping the Gory Details Local<\/h2>\n\n\n\n<p><strong>Encapsulation<\/strong> means bundling data and behaviour into self\u2011contained units.<\/p>\n\n\n\n<p>Take <code>FlowSwitch<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport subprocess, json, os\nfrom interfaces import Switch, Room\n\nclass FlowSwitch(Switch):\n    def __init__(self, Id, Name, InitialState, CurrentState, LatestVersionId, VersionNumber):\n        super().__init__(Id, Name, InitialState)\n        self.CurrentState = CurrentState\n        self.LatestVersionId = LatestVersionId\n        self.VersionNumber = VersionNumber\n\n    def on(self):\n        if self.InitialState:\n            if self.CurrentState == False:\n                self.update(self.VersionNumber)\n                print(f&quot;FlowSwitch-{self.Name} turned on&quot;)\n            else:\n                print(f&quot;FlowSwitch-{self.Name} already on&quot;)\n        else:\n            print(f&quot;FlowSwitch-{self.Name} shouldn&#039;t be on - update skipped&quot;)\n\n\n    def off(self):\n        if(self.CurrentState):\n            self.update(0)\n            print(f&quot;FlowSwitch-{self.Name} turned off&quot;)\n        else:\n            print(f&quot;FlowSwitch-{self.Name} already off&quot;)\n\n    def update(self, version_number):\n        &quot;&quot;&quot;\n        Update a flow definition record with a version number. If input vn is zero, it will be updated as inactive\n        If input vn &gt; 0, it will be updated as active\n        &quot;&quot;&quot;\n        try:\n            query_res = subprocess.run(\n            &#x5B;\n                &quot;sf&quot;, \n                &quot;data&quot;,  \n                &quot;query&quot;, \n                &quot;--query&quot;,\n                f&quot;select Id from FlowDefinition where LatestVersionId=&#039;{self.LatestVersionId}&#039;&quot;,\n                &quot;-t&quot;,\n                &quot;--json&quot;,\n            ], \n            capture_output=True, \n            text=True, \n            check=True\n            )\n            tmp = json.loads(query_res.stdout)\n            flow_id = tmp&#x5B;&#039;result&#039;]&#x5B;&#039;records&#039;]&#x5B;0]&#x5B;&#039;Id&#039;]\n\n            command = &#x5B;\n                &quot;sf&quot;, \n                &quot;data&quot;, \n                &quot;update&quot;,\n                &quot;record&quot;,\n                &quot;-t&quot;,\n                &quot;-s&quot;,\n                &quot;FlowDefinition&quot;,\n                &quot;-i&quot;,\n                f&quot;{flow_id}&quot;,\n                &quot;-v&quot;,\n                f&quot;Metadata=&#039;{{\\&quot;activeVersionNumber\\&quot;:{version_number}}}&#039;&quot;\n            ]\n            print(f&quot;Command:{command}&quot;)\n            #result = subprocess.run(command, capture_output=True, text=True, check=True)\n            print(f&quot;&#039;{self.LatestVersionId}&#039; successfully updated to {version_number}&quot;)\n        except subprocess.CalledProcessError as e:\n            print(f&quot;Error updating flow: {e.stderr}&quot;)\n<\/pre><\/div>\n\n\n<p>Everything needed to manage a single flow lives in this class:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Data<\/strong>: IDs, names, current\/baseline state, version numbers.<\/li>\n\n\n\n<li><strong>Behaviour<\/strong>: <code>on()<\/code>, <code>off()<\/code>, <code>update()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>No other part of the system builds CLI commands or parses flow metadata. Outside code says:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nswitch.off()  # for pre\u2011migration\nswitch.on()   # for post\u2011migration\n<\/pre><\/div>\n\n\n<p>This is where OO stops being theory and starts being practical:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When the Salesforce CLI or FlowDefinition metadata change, you update <code>FlowSwitch.update()<\/code> in one place.<\/li>\n\n\n\n<li>You don\u2019t grep through a pile of functions trying to find every \u201cjust this once\u201d CLI call.<\/li>\n<\/ul>\n\n\n\n<p>Encapsulation keeps the messy bits in small, named boxes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Inheritance: Sharing the Structure, Not the Hacks<\/h2>\n\n\n\n<p><strong>Inheritance<\/strong> lets you define a common shape once and have specific kinds of things follow that shape.<\/p>\n\n\n\n<p>Here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>FlowSwitch<\/code> inherits from <code>Switch<\/code>.<\/li>\n\n\n\n<li><code>FlowRoom<\/code> inherits from <code>Room<\/code>.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass FlowSwitch(Switch):\n    # Must provide on() and off()\n    ...\n\nclass FlowRoom(Room):\n    def __init__(self, obj_id):\n        super().__init__(obj_id)\n        self.switches = self.get_switches()\n    ...\n<\/pre><\/div>\n\n\n<p>This guarantees:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Every switch type (FlowSwitch, ValidationRuleSwitch, etc.) will:<\/li>\n\n\n\n<li>Have <code>Id<\/code>, <code>Name<\/code>, <code>InitialState<\/code>.<\/li>\n\n\n\n<li>Implement <code>on()<\/code> and <code>off()<\/code>.<\/li>\n\n\n\n<li>Every room type (FlowRoom, ValidationRuleRoom, PicklistRoom) will:<\/li>\n\n\n\n<li>Be tied to an object ID.<\/li>\n\n\n\n<li>Implement <code>get_switches()<\/code>, <code>enter()<\/code>, and <code>exit()<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>So when you go to add the next \u201csmall thing\u201d \u2014 say, a new kind of metadata you want to toggle \u2014 you\u2019re not guessing how to shape it. You inherit from <code>Switch<\/code> and <code>Room<\/code> and follow the same pattern.<\/p>\n\n\n\n<p>Inheritance here isn\u2019t about sharing random helper methods; it\u2019s about sharing a <strong>contract<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Polymorphism: One Loop, Many Implementations<\/h2>\n\n\n\n<p><strong>Polymorphism<\/strong> is what lets you call the same method (<code>enter<\/code>, <code>exit<\/code>) on different object types and have each do what\u2019s appropriate.<\/p>\n\n\n\n<p>The <code>House<\/code> class leans on this heavily:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\ndef visit(self):\n    sorted_keys = sorted(self.Rooms.keys())\n    for key in sorted_keys:\n        room = self.Rooms&#x5B;key]\n        room.enter()\n\ndef depart(self):\n    sorted_keys = sorted(self.Rooms.keys())\n    for key in sorted_keys:\n        room = self.Rooms&#x5B;key]\n        room.exit()\n<\/pre><\/div>\n\n\n<p><code>FlowRoom<\/code>, <code>ValidationRuleRoom<\/code>, and <code>PicklistRoom<\/code> all implement <code>enter()<\/code> and <code>exit()<\/code> in their own way:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>FlowRoom.enter()<\/code> uses <code>FlowSwitch.off()<\/code> to deactivate flows.<\/li>\n\n\n\n<li><code>ValidationRuleRoom.enter()<\/code> deactivates validation rules.<\/li>\n\n\n\n<li><code>PicklistRoom.enter()<\/code> relaxes picklists.<\/li>\n<\/ul>\n\n\n\n<p>But <code>House<\/code> doesn\u2019t know (or care) which is which. It just walks rooms and calls <code>enter()<\/code>\/<code>exit()<\/code>.<\/p>\n\n\n\n<p>That\u2019s why adding a new type of switchable thing doesn\u2019t change the migration workflow. You:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create <code>NewThingSwitch<\/code> and <code>NewThingRoom<\/code> subclasses.<\/li>\n\n\n\n<li>Register <code>NewThingRoom<\/code> in <code>create_rooms()<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>The loops in <code>visit()<\/code> and <code>depart()<\/code> remain untouched.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why My Next \u201cSmall Script\u201d Starts with Objects<\/h2>\n\n\n\n<p>After going through this, I don\u2019t start migration tooling with functions anymore. Even if it feels like \u201cjust a script,\u201d I pause and ask:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>What are the <strong>nouns<\/strong> in this problem?<\/li>\n\n\n\n<li>Can I give those nouns clear responsibilities?<\/li>\n\n\n\n<li>Where can I hide the messy details so they don\u2019t leak everywhere?<\/li>\n<\/ul>\n\n\n\n<p>In this case, those nouns were <strong>House<\/strong>, <strong>Room<\/strong>, and <strong>Switch<\/strong>, backed by solid OO concepts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Abstraction<\/strong>: the main flow talks about visiting houses and rooms, not about CLI flags or JSON structures.<\/li>\n\n\n\n<li><strong>Encapsulation<\/strong>: each switch knows how to manage itself; the rest of the system doesn\u2019t need to.<\/li>\n\n\n\n<li><strong>Inheritance<\/strong>: rooms and switches share a predictable structure without copy\u2011pasting code.<\/li>\n\n\n\n<li><strong>Polymorphism<\/strong>: one simple loop (<code>for room in Rooms<\/code>) drives very different behaviours.<\/li>\n<\/ul>\n\n\n\n<p>The end result isn\u2019t just \u201cmore OO.\u201d It\u2019s code I can:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Read quickly when a migration is urgent.<\/li>\n\n\n\n<li>Change safely when Salesforce changes.<\/li>\n\n\n\n<li>Extend without rewriting the core logic.<\/li>\n<\/ul>\n\n\n\n<p>That\u2019s why my next \u201csmall script\u201d starts with objects, not functions. It\u2019s not about impressing anyone with design patterns; it\u2019s about not dreading the next time I have to touch the code.<\/p>\n\n\n\n<p>And if you\u2019ve ever opened an old \u201cquick script\u201d and wondered <strong>\u201cwhat on earth was I thinking?\u201d<\/strong>, that shift might be worth trying in your next one, too.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It always starts the same way. You just need a small script. In my case, it was \u201cjust\u201d a helper&#46;&#46;&#46;<\/p>\n","protected":false},"author":1,"featured_media":96,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_import_markdown_pro_load_document_selector":0,"_import_markdown_pro_submit_text_textarea":"","footnotes":""},"categories":[5],"tags":[],"class_list":["post-95","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-productivity-without-the-hype"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/joeltan.me\/?p=95\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs\" \/>\n<meta property=\"og:description\" content=\"It always starts the same way. You just need a small script. In my case, it was \u201cjust\u201d a helper&#046;&#046;&#046;\" \/>\n<meta property=\"og:url\" content=\"https:\/\/joeltan.me\/?p=95\" \/>\n<meta property=\"og:site_name\" content=\"Joel Tan Tech Blogs\" \/>\n<meta property=\"article:published_time\" content=\"2025-11-20T20:28:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-01-26T11:37:55+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2048\" \/>\n\t<meta property=\"og:image:height\" content=\"2560\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Joel Tan\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Joel Tan\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/joeltan.me\/?p=95#article\",\"isPartOf\":{\"@id\":\"https:\/\/joeltan.me\/?p=95\"},\"author\":{\"name\":\"Joel Tan\",\"@id\":\"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743\"},\"headline\":\"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions\",\"datePublished\":\"2025-11-20T20:28:00+00:00\",\"dateModified\":\"2026-01-26T11:37:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/joeltan.me\/?p=95\"},\"wordCount\":1115,\"commentCount\":0,\"image\":{\"@id\":\"https:\/\/joeltan.me\/?p=95#primaryimage\"},\"thumbnailUrl\":\"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg\",\"articleSection\":[\"Productivity Without the Hype\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/joeltan.me\/?p=95#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/joeltan.me\/?p=95\",\"url\":\"https:\/\/joeltan.me\/?p=95\",\"name\":\"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs\",\"isPartOf\":{\"@id\":\"https:\/\/joeltan.me\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/joeltan.me\/?p=95#primaryimage\"},\"image\":{\"@id\":\"https:\/\/joeltan.me\/?p=95#primaryimage\"},\"thumbnailUrl\":\"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg\",\"datePublished\":\"2025-11-20T20:28:00+00:00\",\"dateModified\":\"2026-01-26T11:37:55+00:00\",\"author\":{\"@id\":\"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743\"},\"breadcrumb\":{\"@id\":\"https:\/\/joeltan.me\/?p=95#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/joeltan.me\/?p=95\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/joeltan.me\/?p=95#primaryimage\",\"url\":\"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg\",\"contentUrl\":\"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg\",\"width\":2048,\"height\":2560,\"caption\":\"Small model rocket flying up in smoke after launch on green field with hay rolls\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/joeltan.me\/?p=95#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/joeltan.me\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/joeltan.me\/#website\",\"url\":\"https:\/\/joeltan.me\/\",\"name\":\"Joel Tan Tech Blogs\",\"description\":\"Building systems that survive real life\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/joeltan.me\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743\",\"name\":\"Joel Tan\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/joeltan.me\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d9b5d1ab218cb2478280027d371ea60543f6551132d31a8cbd45a5a5b3fbadc9?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d9b5d1ab218cb2478280027d371ea60543f6551132d31a8cbd45a5a5b3fbadc9?s=96&d=mm&r=g\",\"caption\":\"Joel Tan\"},\"sameAs\":[\"http:\/\/192.168.1.146\"],\"url\":\"https:\/\/joeltan.me\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/joeltan.me\/?p=95","og_locale":"en_US","og_type":"article","og_title":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs","og_description":"It always starts the same way. You just need a small script. In my case, it was \u201cjust\u201d a helper&#46;&#46;&#46;","og_url":"https:\/\/joeltan.me\/?p=95","og_site_name":"Joel Tan Tech Blogs","article_published_time":"2025-11-20T20:28:00+00:00","article_modified_time":"2026-01-26T11:37:55+00:00","og_image":[{"width":2048,"height":2560,"url":"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg","type":"image\/jpeg"}],"author":"Joel Tan","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Joel Tan","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/joeltan.me\/?p=95#article","isPartOf":{"@id":"https:\/\/joeltan.me\/?p=95"},"author":{"name":"Joel Tan","@id":"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743"},"headline":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions","datePublished":"2025-11-20T20:28:00+00:00","dateModified":"2026-01-26T11:37:55+00:00","mainEntityOfPage":{"@id":"https:\/\/joeltan.me\/?p=95"},"wordCount":1115,"commentCount":0,"image":{"@id":"https:\/\/joeltan.me\/?p=95#primaryimage"},"thumbnailUrl":"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg","articleSection":["Productivity Without the Hype"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/joeltan.me\/?p=95#respond"]}]},{"@type":"WebPage","@id":"https:\/\/joeltan.me\/?p=95","url":"https:\/\/joeltan.me\/?p=95","name":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions - Joel Tan Tech Blogs","isPartOf":{"@id":"https:\/\/joeltan.me\/#website"},"primaryImageOfPage":{"@id":"https:\/\/joeltan.me\/?p=95#primaryimage"},"image":{"@id":"https:\/\/joeltan.me\/?p=95#primaryimage"},"thumbnailUrl":"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg","datePublished":"2025-11-20T20:28:00+00:00","dateModified":"2026-01-26T11:37:55+00:00","author":{"@id":"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743"},"breadcrumb":{"@id":"https:\/\/joeltan.me\/?p=95#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/joeltan.me\/?p=95"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/joeltan.me\/?p=95#primaryimage","url":"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg","contentUrl":"https:\/\/joeltan.me\/wp-content\/uploads\/2026\/01\/small-model-rocket-flying-up-in-smoke-after-launch-on-green-field-with-hay-rolls-4278308-scaled.jpg","width":2048,"height":2560,"caption":"Small model rocket flying up in smoke after launch on green field with hay rolls"},{"@type":"BreadcrumbList","@id":"https:\/\/joeltan.me\/?p=95#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/joeltan.me\/"},{"@type":"ListItem","position":2,"name":"Why My Next \u2018Small Script\u2019 Starts with Objects, Not Functions"}]},{"@type":"WebSite","@id":"https:\/\/joeltan.me\/#website","url":"https:\/\/joeltan.me\/","name":"Joel Tan Tech Blogs","description":"Building systems that survive real life","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/joeltan.me\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/joeltan.me\/#\/schema\/person\/db13342201787db723bfdeadcd792743","name":"Joel Tan","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/joeltan.me\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d9b5d1ab218cb2478280027d371ea60543f6551132d31a8cbd45a5a5b3fbadc9?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d9b5d1ab218cb2478280027d371ea60543f6551132d31a8cbd45a5a5b3fbadc9?s=96&d=mm&r=g","caption":"Joel Tan"},"sameAs":["http:\/\/192.168.1.146"],"url":"https:\/\/joeltan.me\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/posts\/95","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joeltan.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=95"}],"version-history":[{"count":1,"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/posts\/95\/revisions"}],"predecessor-version":[{"id":97,"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/posts\/95\/revisions\/97"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/joeltan.me\/index.php?rest_route=\/wp\/v2\/media\/96"}],"wp:attachment":[{"href":"https:\/\/joeltan.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=95"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joeltan.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=95"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joeltan.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=95"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}