[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-adhqrcNeBlT9AooEBfpvTryELqQR-eF-Wjhp4CTkMDs":3,"sanity-sQ4lmp-plg1FaWrBDBaYnrNFxzI7mZISchaAC_LcGIo":1135},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":1110},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"4d0175f4-40a8-47eb-9bb3-a453b326aa7d","2026-07-03T07:40:00.000Z",{"_type":10,"current":11},"slug","the-good-the-bad-and-the-ai-apps",null,"The good, the bad, and the AI apps",[15,21,27,33],{"_id":16,"publishedAt":17,"slug":18,"title":20},"eb5b66eb-9410-4329-83bb-22bbff39402a","2026-04-28T13:00:00.000Z",{"_type":10,"current":19},"turn-scattered-knowledge-into-trusted-intelligence","Turning scattered knowledge into trusted intelligence: Stack Internal 2026.3",{"_id":22,"publishedAt":23,"slug":24,"title":26},"369c2401-b62e-4a37-8ff8-bf603023ecad","2026-03-02T15:03:00.988Z",{"_type":10,"current":25},"what-s-new-at-stack-overflow-march-2026","What’s new at Stack Overflow: March 2026",{"_id":28,"publishedAt":29,"slug":30,"title":32},"5e9053a4-07ea-447c-91ea-29e0b6228537","2026-02-02T15:00:00.000Z",{"_type":10,"current":31},"what-s-new-at-stack-overflow-february-2026","What’s new at Stack Overflow: February 2026",{"_id":34,"publishedAt":35,"slug":36,"title":38},"a1b538eb-a8a6-46d0-80a1-ac70ec9bb935","2026-01-05T10:00:00.000-05:00",{"_type":10,"current":37},"what-s-new-at-stack-overflow-january-2026","What’s new at Stack Overflow: January 2026",{"_createdAt":40,"_id":41,"_rev":42,"_system":43,"_type":46,"_updatedAt":47,"author":48,"body":65,"comments":1074,"dateUrl":1075,"excerpt":1076,"image":1077,"legacyBody":1080,"product":12,"publishedAt":1083,"slug":1084,"sponsored":12,"tags":1086,"title":1109,"visible":1074},"2023-05-24T12:51:00Z","wp-post-20131","2CozUHl7bja0z7No5holce",{"base":44},{"id":41,"rev":45},"Soh4zwnVSgD0IUkr59n0oB","blogPost","2026-05-18T19:47:50Z",[49],{"_createdAt":50,"_id":51,"_rev":52,"_type":53,"_updatedAt":54,"avatar":55,"bio":60,"employee":61,"name":62,"slug":63},"2023-05-23T16:27:18Z","wp-author-cap-18115","07ZbrKPSUrjrV4wQ6fDpaa","blogAuthor","2023-06-20T15:05:10Z",{"_type":56,"asset":57},"image",{"_ref":58,"_type":59},"image-8cf69a05d6ef9dfd6db2ac2da2518a7d5e4b90d1-386x426-png","reference","Daniel Orner is a Staff Software Engineer at Flipp and has previously worked at IBM. You can find his articles, ranging from musings on event-driven microservices to what Ruby can learn from Go, at his GitHub profile. He lives in Toronto with a wife and four small people who alternate between enriching and enraging him.","none","Daniel Orner",{"current":64},"daniel-orner",[66,95,103,111,120,136,144,152,170,185,200,215,230,238,258,266,274,293,301,306,314,322,330,339,350,374,382,390,457,511,519,535,543,547,555,563,589,616,624,628,636,644,660,668,671,679,682,690,693,701,717,725,728,736,739,755,763,771,775,783,791,807,826,834,838,854,862,878,922,938,954,962,965,981,989,993,1001,1031,1039,1047],{"_key":67,"_type":68,"children":69,"markDefs":93,"style":94},"c93f1eb04997","block",[70,75,80,84,89],{"_key":71,"_type":72,"marks":73,"text":74},"c93f1eb049970","span",[],"The concept of ",{"_key":76,"_type":72,"marks":77,"text":79},"c93f1eb049971",[78],"strong","future-proofing",{"_key":81,"_type":72,"marks":82,"text":83},"c93f1eb049972",[]," your code is one that perennially pops up in conversations about software. It sounds magical—who ",{"_key":85,"_type":72,"marks":86,"text":88},"c93f1eb049973",[87],"em","wouldn't",{"_key":90,"_type":72,"marks":91,"text":92},"c93f1eb049974",[]," want to make your code impervious to the future?",[],"normal",{"_key":96,"_type":68,"children":97,"markDefs":102,"style":94},"d7c4edde04a8",[98],{"_key":99,"_type":72,"marks":100,"text":101},"d7c4edde04a80",[],"Of course, the reality is much less rosy and much messier.",[],{"_key":104,"_type":68,"children":105,"markDefs":110,"style":94},"26a9e07509b4",[106],{"_key":107,"_type":72,"marks":108,"text":109},"26a9e07509b40",[],"In this article, I'm going to discuss what I think people mean by \"future-proofing,\" how you might be able to accomplish it, and when and why it might be a bad choice.",[],{"_key":112,"_type":68,"children":113,"markDefs":118,"style":119},"a4ae1333b9b0",[114],{"_key":115,"_type":72,"marks":116,"text":117},"a4ae1333b9b00",[],"How does one \"proof\" one's \"future?\"",[],"h2",{"_key":121,"_type":68,"children":122,"markDefs":135,"style":94},"f88fe602c88e",[123,127,131],{"_key":124,"_type":72,"marks":125,"text":126},"f88fe602c88e0",[],"You can think of future-proofing more accurately as ",{"_key":128,"_type":72,"marks":129,"text":130},"f88fe602c88e1",[78],"change-proofing",{"_key":132,"_type":72,"marks":133,"text":134},"f88fe602c88e2",[],". We can define it as:",[],{"_key":137,"_type":68,"children":138,"markDefs":143,"style":94},"ea3188fc1926",[139],{"_key":140,"_type":72,"marks":141,"text":142},"ea3188fc19260",[87],"\"Making a design, architecture, or programming decision that allows for future changes to be easier to manage, take less time or result in fewer changes to the overall code.\"",[],{"_key":145,"_type":68,"children":146,"markDefs":151,"style":94},"9c5355d8e3c3",[147],{"_key":148,"_type":72,"marks":149,"text":150},"9c5355d8e3c30",[],"These changes can fall into a number of different categories:",[],{"_key":153,"_type":68,"children":154,"level":167,"listItem":168,"markDefs":169,"style":94},"7a98677e4f6e",[155,159,163],{"_key":156,"_type":72,"marks":157,"text":158},"7a98677e4f6e0",[],"Changes to ",{"_key":160,"_type":72,"marks":161,"text":162},"7a98677e4f6e1",[78],"scale",{"_key":164,"_type":72,"marks":165,"text":166},"7a98677e4f6e2",[]," - the project has to do more of the things it's doing, whether that means handling more traffic or work items, processing them faster, etc.",1,"bullet",[],{"_key":171,"_type":68,"children":172,"level":167,"listItem":168,"markDefs":184,"style":94},"6e32f6c59be2",[173,176,180],{"_key":174,"_type":72,"marks":175,"text":158},"6e32f6c59be20",[],{"_key":177,"_type":72,"marks":178,"text":179},"6e32f6c59be21",[78],"requirements",{"_key":181,"_type":72,"marks":182,"text":183},"6e32f6c59be22",[]," - new information has entered the business (or has entered the engineering team from the business) and now the system needs to change to accommodate them.",[],{"_key":186,"_type":68,"children":187,"level":167,"listItem":168,"markDefs":199,"style":94},"a04ed4b0797a",[188,191,195],{"_key":189,"_type":72,"marks":190,"text":158},"a04ed4b0797a0",[],{"_key":192,"_type":72,"marks":193,"text":194},"a04ed4b0797a1",[78],"technology stack",{"_key":196,"_type":72,"marks":197,"text":198},"a04ed4b0797a2",[]," - switching to a different data store or programming language, for example.",[],{"_key":201,"_type":68,"children":202,"level":167,"listItem":168,"markDefs":214,"style":94},"05d566950fcb",[203,206,210],{"_key":204,"_type":72,"marks":205,"text":158},"05d566950fcb0",[],{"_key":207,"_type":72,"marks":208,"text":209},"05d566950fcb1",[78],"integrations",{"_key":211,"_type":72,"marks":212,"text":213},"05d566950fcb2",[]," - the project needs to talk to a new third-party application, either on top of or instead of an existing one.",[],{"_key":216,"_type":68,"children":217,"level":167,"listItem":168,"markDefs":229,"style":94},"90f4d557b98d",[218,221,225],{"_key":219,"_type":72,"marks":220,"text":158},"90f4d557b98d0",[],{"_key":222,"_type":72,"marks":223,"text":224},"90f4d557b98d1",[78],"schemas",{"_key":226,"_type":72,"marks":227,"text":228},"90f4d557b98d2",[]," - we want to change the fields that define our data objects.",[],{"_key":231,"_type":68,"children":232,"markDefs":237,"style":119},"175e60feb142",[233],{"_key":234,"_type":72,"marks":235,"text":236},"175e60feb1420",[],"Should I stay or should I grow?",[],{"_key":239,"_type":68,"children":240,"markDefs":254,"style":94},"a16e9acff66b",[241,245,250],{"_key":242,"_type":72,"marks":243,"text":244},"a16e9acff66b0",[],"The main issue with future-proofing is that it runs slap-bang into one of the central tenets of software engineering: YAGNI, or ",{"_key":246,"_type":72,"marks":247,"text":249},"a16e9acff66b1",[248,78],"0d7d14cee91f","You Ain't Gonna Need It",{"_key":251,"_type":72,"marks":252,"text":253},"a16e9acff66b2",[],".",[255],{"_key":248,"_type":256,"href":257,"reference":12},"link","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FYou_aren%27t_gonna_need_it",{"_key":259,"_type":68,"children":260,"markDefs":265,"style":94},"b25ae9c5fb47",[261],{"_key":262,"_type":72,"marks":263,"text":264},"b25ae9c5fb470",[],"This principle states that until you actually know you're going to have a change, you shouldn’t code your software in a way that anticipates that change. Violating this principle results in bloat, confusing and unnecessary abstractions that make the code harder to understand, and often mounds of tech debt.",[],{"_key":267,"_type":68,"children":268,"markDefs":273,"style":94},"d1350ffd2035",[269],{"_key":270,"_type":72,"marks":271,"text":272},"d1350ffd20350",[],"However, if you never cast your eyes to the future, you really can run into issues where just a little extra bit of work upfront could have saved you months of it down the road.",[],{"_key":275,"_type":68,"children":276,"markDefs":290,"style":94},"5a6175284d4c",[277,281,286],{"_key":278,"_type":72,"marks":279,"text":280},"5a6175284d4c0",[],"So... should you future-proof your code? The answer, as with practically everything in software, is, \"",{"_key":282,"_type":72,"marks":283,"text":285},"5a6175284d4c1",[284],"ff4d8ca73d61","it depends",{"_key":287,"_type":72,"marks":288,"text":289},"5a6175284d4c2",[],".\"\n",[291],{"_key":284,"_type":256,"href":292,"reference":12},"https:\u002F\u002Fmedium.com\u002Fswlh\u002Fthe-golden-rule-of-software-engineering-9faaaab85e78",{"_key":294,"_type":68,"children":295,"markDefs":300,"style":94},"5b266301e1a0",[296],{"_key":297,"_type":72,"marks":298,"text":299},"5b266301e1a00",[],"I see this as a spectrum of sorts:",[],{"_key":302,"_type":56,"alt":12,"asset":303,"caption":305,"markDefs":12},"c16c4eb8e6e9",{"_ref":304,"_type":59},"image-831a8f907aaac00041bdc376cd316034b41cd552-766x40-png","",{"_key":307,"_type":68,"children":308,"markDefs":313,"style":94},"367d8daa8917",[309],{"_key":310,"_type":72,"marks":311,"text":312},"367d8daa89170",[],"Every situation is unique, of course. But some kinds of changes are much less likely to deserve future-proofing, while in others, the extra work is more likely to pay off and (just as importantly) not result in significant downsides.",[],{"_key":315,"_type":68,"children":316,"markDefs":321,"style":119},"c2c7b6810ad4",[317],{"_key":318,"_type":72,"marks":319,"text":320},"c2c7b6810ad40",[],"Future-proofing strategies",[],{"_key":323,"_type":68,"children":324,"markDefs":329,"style":94},"896b817e9872",[325],{"_key":326,"_type":72,"marks":327,"text":328},"896b817e98720",[],"In general, any time we want to protect ourselves from future changes, we would engage in one of two strategies.",[],{"_key":331,"_type":68,"children":332,"markDefs":337,"style":338},"c7db9f66f5b8",[333],{"_key":334,"_type":72,"marks":335,"text":336},"c7db9f66f5b80",[],"Modularization",[],"h3",{"_key":340,"_type":68,"children":341,"markDefs":349,"style":94},"68132762d1c4",[342,345],{"_key":343,"_type":72,"marks":344,"text":336},"68132762d1c40",[78],{"_key":346,"_type":72,"marks":347,"text":348},"68132762d1c41",[]," is the act of splitting your code up into smaller chunks. Every piece of software has some level of modularization—otherwise, your code would consist of a single enormous file that had nothing but unrolled functions and primitive types. (These programs do exist, but no one in their right mind wants anything to do with them.)",[],{"_key":351,"_type":68,"children":352,"markDefs":373,"style":94},"339eae9207bd",[353,357,361,365,369],{"_key":354,"_type":72,"marks":355,"text":356},"339eae9207bd0",[],"Increasing modularization allows you to ",{"_key":358,"_type":72,"marks":359,"text":360},"339eae9207bd1",[78],"isolate",{"_key":362,"_type":72,"marks":363,"text":364},"339eae9207bd2",[]," your changes to a single module, and\u002For to more easily ",{"_key":366,"_type":72,"marks":367,"text":368},"339eae9207bd3",[78],"swap",{"_key":370,"_type":72,"marks":371,"text":372},"339eae9207bd4",[]," modules out for each other.",[],{"_key":375,"_type":68,"children":376,"markDefs":381,"style":94},"65a9147dc45e",[377],{"_key":378,"_type":72,"marks":379,"text":380},"65a9147dc45e0",[],"You can also have modularization at different levels. Extracting code into a function is a simple act of modularization that rarely has a downside until you're at the point where your functions are so small and numerous that they're hard to keep track of. However, you can extract things into a class, a sub-application, a microservice, or even a separate cluster; and at each level, while the isolation and flexibility goes up, so does the cognitive and administrative overhead of managing your different components.",[],{"_key":383,"_type":68,"children":384,"markDefs":389,"style":338},"ac0b42c723ac",[385],{"_key":386,"_type":72,"marks":387,"text":388},"ac0b42c723ac0",[],"Abstraction",[],{"_key":391,"_type":68,"children":392,"markDefs":456,"style":94},"6cbe66552ef5",[393,396,400,404,408,412,416,420,424,429,433,437,441,445,449,452],{"_key":394,"_type":72,"marks":395,"text":388},"6cbe66552ef50",[78],{"_key":397,"_type":72,"marks":398,"text":399},"6cbe66552ef51",[]," is the mental model you have of the parts of your code. A module, function, class, etc. with ",{"_key":401,"_type":72,"marks":402,"text":403},"6cbe66552ef52",[78],"low abstraction",{"_key":405,"_type":72,"marks":406,"text":407},"6cbe66552ef53",[]," (or higher ",{"_key":409,"_type":72,"marks":410,"text":411},"6cbe66552ef54",[78],"specificity",{"_key":413,"_type":72,"marks":414,"text":415},"6cbe66552ef55",[],") more closely models the action it takes or object it represents. A thing with ",{"_key":417,"_type":72,"marks":418,"text":419},"6cbe66552ef56",[78],"higher abstraction",{"_key":421,"_type":72,"marks":422,"text":423},"6cbe66552ef57",[]," brings your reasoning up a level. For example, instead of your code working with ",{"_key":425,"_type":72,"marks":426,"text":428},"6cbe66552ef58",[427],"code","Cars",{"_key":430,"_type":72,"marks":431,"text":432},"6cbe66552ef59",[],", it could work with ",{"_key":434,"_type":72,"marks":435,"text":436},"6cbe66552ef510",[427],"Vehicles",{"_key":438,"_type":72,"marks":439,"text":440},"6cbe66552ef511",[],". That way, if you ever need to start handling ",{"_key":442,"_type":72,"marks":443,"text":444},"6cbe66552ef512",[427],"Motorcycles",{"_key":446,"_type":72,"marks":447,"text":448},"6cbe66552ef513",[],", the change necessary to do so is mitigated by the fact that you were never really talking about ",{"_key":450,"_type":72,"marks":451,"text":428},"6cbe66552ef514",[427],{"_key":453,"_type":72,"marks":454,"text":455},"6cbe66552ef515",[]," to begin with.",[],{"_key":458,"_type":68,"children":459,"markDefs":510,"style":94},"d27ea9490cc7",[460,464,468,472,475,479,482,486,490,494,498,502,506],{"_key":461,"_type":72,"marks":462,"text":463},"d27ea9490cc70",[],"The higher you go with abstraction, the more flexible you make your code, but also the harder it is to understand. Everyone knows what a ",{"_key":465,"_type":72,"marks":466,"text":467},"d27ea9490cc71",[427],"Car",{"_key":469,"_type":72,"marks":470,"text":471},"d27ea9490cc72",[]," is, but when you start working with ",{"_key":473,"_type":72,"marks":474,"text":436},"d27ea9490cc73",[427],{"_key":476,"_type":72,"marks":477,"text":478},"d27ea9490cc74",[],", it becomes more difficult to work with ",{"_key":480,"_type":72,"marks":481,"text":467},"d27ea9490cc75",[427],{"_key":483,"_type":72,"marks":484,"text":485},"d27ea9490cc76",[],"-specific things like booster seats and steering wheels. And if you keep going higher up the abstraction tree, you might find yourself coding around ",{"_key":487,"_type":72,"marks":488,"text":489},"d27ea9490cc77",[427],"Transportation",{"_key":491,"_type":72,"marks":492,"text":493},"d27ea9490cc78",[],", ",{"_key":495,"_type":72,"marks":496,"text":497},"d27ea9490cc79",[427],"ManufacturedGoods",{"_key":499,"_type":72,"marks":500,"text":501},"d27ea9490cc710",[],", even plain old ",{"_key":503,"_type":72,"marks":504,"text":505},"d27ea9490cc711",[427],"Objects",{"_key":507,"_type":72,"marks":508,"text":509},"d27ea9490cc712",[],", and have a very hard time figuring out how to fill up your tank.",[],{"_key":512,"_type":68,"children":513,"markDefs":518,"style":119},"7d509bcf0406",[514],{"_key":515,"_type":72,"marks":516,"text":517},"7d509bcf04060",[],"Types of changes",[],{"_key":520,"_type":68,"children":521,"markDefs":534,"style":94},"3e1a6ffa16e6",[522,526,530],{"_key":523,"_type":72,"marks":524,"text":525},"3e1a6ffa16e60",[],"Lets go through each of the types of changes I listed above. For each, I’m recommending where on the spectrum your future-proofing efforts might lie. In general, I will be applying these suggestions for when you have ",{"_key":527,"_type":72,"marks":528,"text":529},"3e1a6ffa16e61",[78],"no current indication that the change will happen",{"_key":531,"_type":72,"marks":532,"text":533},"3e1a6ffa16e62",[],". As the change becomes more likely, you'd move further towards the left side of the line.",[],{"_key":536,"_type":68,"children":537,"markDefs":542,"style":338},"7cf527d1f0d6",[538],{"_key":539,"_type":72,"marks":540,"text":541},"7cf527d1f0d60",[],"Changes to scale",[],{"_key":544,"_type":56,"alt":12,"asset":545,"caption":305,"markDefs":12},"04e7ef9537f5",{"_ref":546,"_type":59},"image-19e8f57f06bb4e8cdd0d15da82a43f8d3de1a075-766x40-png",{"_key":548,"_type":68,"children":549,"markDefs":554,"style":94},"1708f46eb122",[550],{"_key":551,"_type":72,"marks":552,"text":553},"1708f46eb1220",[],"Making your code and architecture robust and able to handle whatever you throw at it is a core facet of design. However, part of that architecture and design has to be what kind of traffic you expect it to have. The higher scale you have to handle, in general, the more complex your architecture needs to be.",[],{"_key":556,"_type":68,"children":557,"markDefs":562,"style":94},"1f762a54f07a",[558],{"_key":559,"_type":72,"marks":560,"text":561},"1f762a54f07a0",[],"Making a simple app that's designed to be used by a handful of internal users could be done via a Rails or Django application with a minimum of JavaScript, for example. But if you need to be able to handle thousands or millions of concurrent users, that's just not going to cut it. You're going to need lightning-fast services or micro-services, horizontal scaling and auto-scaling, and probably a more responsive front-end and more tailored and complex caching mechanisms.",[],{"_key":564,"_type":68,"children":565,"markDefs":586,"style":94},"eb8eca9544fd",[566,570,574,578,583],{"_key":567,"_type":72,"marks":568,"text":569},"eb8eca9544fd0",[],"Over-architecting all of this upfront when you have no anticipation of your app actually scaling to that size is flagrant YAGNI. In fact, building your system deliberately for your current scale ",{"_key":571,"_type":72,"marks":572,"text":573},"eb8eca9544fd1",[87],"even if you know you may have to rewrite it when the scale changes",{"_key":575,"_type":72,"marks":576,"text":577},"eb8eca9544fd2",[]," is a perfectly valid strategy, sometimes called ",{"_key":579,"_type":72,"marks":580,"text":582},"eb8eca9544fd3",[581],"f05d8e392498","sacrificial architecture",{"_key":584,"_type":72,"marks":585,"text":253},"eb8eca9544fd4",[],[587],{"_key":581,"_type":256,"href":588,"reference":12},"https:\u002F\u002Fstackoverflow.blog\u002F2021\u002F03\u002F01\u002Fsacrificial-architecture-learning-from-abandoned-systems\u002F",{"_key":590,"_type":68,"children":591,"markDefs":613,"style":94},"db3e9f04c0c6",[592,596,600,604,609],{"_key":593,"_type":72,"marks":594,"text":595},"db3e9f04c0c60",[],"Having said that, there are ",{"_key":597,"_type":72,"marks":598,"text":599},"db3e9f04c0c61",[87],"some",{"_key":601,"_type":72,"marks":602,"text":603},"db3e9f04c0c62",[]," scaling considerations you should take into account. Leaving in lots of ",{"_key":605,"_type":72,"marks":606,"text":608},"db3e9f04c0c63",[607],"91c27bb8c88a","N+1 queries",{"_key":610,"_type":72,"marks":611,"text":612},"db3e9f04c0c64",[]," or having unnecessarily large or frequent requests is bad engineering practice all around and will leave a bad taste in your users' mouths—regardless of how many or few they are.",[614],{"_key":607,"_type":256,"href":615,"reference":12},"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F97197\u002Fwhat-is-the-n1-selects-problem-in-orm-object-relational-mapping",{"_key":617,"_type":68,"children":618,"markDefs":623,"style":338},"ae85a114e56e",[619],{"_key":620,"_type":72,"marks":621,"text":622},"ae85a114e56e0",[],"Changes to requirements",[],{"_key":625,"_type":56,"alt":12,"asset":626,"caption":305,"markDefs":12},"b2d51c71a217",{"_ref":627,"_type":59},"image-c8b56d065e8227a1f36e6a30d2b5461bc686595e-766x40-png",{"_key":629,"_type":68,"children":630,"markDefs":635,"style":94},"7be5d0c595c8",[631],{"_key":632,"_type":72,"marks":633,"text":634},"7be5d0c595c80",[],"This case (where your code has to change or add to its behavior) is the hardest one to guess at, as well as the one most likely to happen.",[],{"_key":637,"_type":68,"children":638,"markDefs":643,"style":94},"0c32c2ea3fa5",[639],{"_key":640,"_type":72,"marks":641,"text":642},"0c32c2ea3fa50",[],"This is a case where general modularization can help you, but I'd recommend against trying to prematurely add abstraction to your code.",[],{"_key":645,"_type":68,"children":646,"markDefs":659,"style":94},"c4a92f7336f8",[647,651,655],{"_key":648,"_type":72,"marks":649,"text":650},"c4a92f7336f80",[],"In particular, if we're talking about ",{"_key":652,"_type":72,"marks":653,"text":654},"c4a92f7336f81",[87],"business rules",{"_key":656,"_type":72,"marks":657,"text":658},"c4a92f7336f82",[],", isolating the rules themselves in a way that makes them easy to test and change is an overall good practice.",[],{"_key":661,"_type":68,"children":662,"markDefs":667,"style":94},"7aec8189c2fb",[663],{"_key":664,"_type":72,"marks":665,"text":666},"7aec8189c2fb0",[],"As a simple example, here's some code working with a product that has some rules around what a valid product can be considered:",[],{"_key":669,"_type":427,"code":670,"markDefs":12},"f8a1f0643f1e","product.save if product.price >= 0 && !product.name.nil?",{"_key":672,"_type":68,"children":673,"markDefs":678,"style":94},"1806bfdd1cdd",[674],{"_key":675,"_type":72,"marks":676,"text":677},"1806bfdd1cdd0",[],"Products might be saved from many different places in your application. Rather than the calling code checking these business rules, we can extract them to their own method:",[],{"_key":680,"_type":427,"code":681,"markDefs":12},"da10d2fd17ec","class Product\n  def valid?\n    self.price >= 0 && !self.name.nil?\n  end\nend\nproduct.save if product.valid?",{"_key":683,"_type":68,"children":684,"markDefs":689,"style":94},"54750a0539e1",[685],{"_key":686,"_type":72,"marks":687,"text":688},"54750a0539e10",[],"We can even go further and have a separate class or module that handles this check:",[],{"_key":691,"_type":427,"code":692,"markDefs":12},"bfc37ba97357","module ProductValidator\n  def self.valid?(product)\n    product.price >= 0 && !product.price.nil?\n  end\nend\nproduct.save if ProductValidator.valid?(product)",{"_key":694,"_type":68,"children":695,"markDefs":700,"style":94},"f24bc812967c",[696],{"_key":697,"_type":72,"marks":698,"text":699},"f24bc812967c0",[],"These are relatively minor changes and keep your code clean and easily testable, and can isolate further changes of this sort to the one place.",[],{"_key":702,"_type":68,"children":703,"markDefs":716,"style":94},"7749d67b7f62",[704,708,712],{"_key":705,"_type":72,"marks":706,"text":707},"7749d67b7f620",[],"The key words are ",{"_key":709,"_type":72,"marks":710,"text":711},"7749d67b7f621",[87],"\"of this sort\"",{"_key":713,"_type":72,"marks":714,"text":715},"7749d67b7f622",[],", however. If the requirements changes involve completely changing how the behavior of a feature acts, or adding new features, that's where you want to start putting your foot down more firmly on the YAGNI side of things and only building what you actually know.",[],{"_key":718,"_type":68,"children":719,"markDefs":724,"style":94},"98e0571a943d",[720],{"_key":721,"_type":72,"marks":722,"text":723},"98e0571a943d0",[],"Let's imagine that your product can be purchased:",[],{"_key":726,"_type":427,"code":727,"markDefs":12},"919911c60920","class Product\n  def purchase\n    # contact the purchase service and complete transaction\n  end\nend",{"_key":729,"_type":68,"children":730,"markDefs":735,"style":94},"8e9e02340f46",[731],{"_key":732,"_type":72,"marks":733,"text":734},"8e9e02340f460",[],"Wait a minute, you reason. We might eventually expand into not only buying products but also selling them! Maybe renting them? We should abstract this out:",[],{"_key":737,"_type":427,"code":738,"markDefs":12},"ff29aa82e920","class Product\n  def perform_action\n  end\nend\n\nclass PurchasableProduct\n  def perform_action\n    # contact the purchase service and complete transaction\n  end\nend",{"_key":740,"_type":68,"children":741,"markDefs":754,"style":94},"74bb803c395c",[742,746,750],{"_key":743,"_type":72,"marks":744,"text":745},"74bb803c395c0",[],"This is classic YAGNI over-abstraction. People reading your code won't see you purchasing a product, you're \"performing an action\" on something that ",{"_key":747,"_type":72,"marks":748,"text":749},"74bb803c395c1",[87],"resolves",{"_key":751,"_type":72,"marks":752,"text":753},"74bb803c395c2",[]," to purchasing.",[],{"_key":756,"_type":68,"children":757,"markDefs":762,"style":94},"a853425c0191",[758],{"_key":759,"_type":72,"marks":760,"text":761},"a853425c01910",[],"You have no reason to believe that your business actually will expand into further operations. You've introduced a level of abstraction that removes your code one extra step from what's actually happening and what's being modeled.",[],{"_key":764,"_type":68,"children":765,"markDefs":770,"style":338},"5528e346830e",[766],{"_key":767,"_type":72,"marks":768,"text":769},"5528e346830e0",[],"Changes to technology stack",[],{"_key":772,"_type":56,"alt":12,"asset":773,"caption":305,"markDefs":12},"c93533082e18",{"_ref":774,"_type":59},"image-d40e9c573bd758860984b820fa3337b3e7ff261c-766x40-png",{"_key":776,"_type":68,"children":777,"markDefs":782,"style":94},"54290e3a4aa9",[778],{"_key":779,"_type":72,"marks":780,"text":781},"54290e3a4aa90",[],"I'll come straight out and say it—there is just no good reason to build your app in a way that you plan to eventually change your basic technology stack.",[],{"_key":784,"_type":68,"children":785,"markDefs":790,"style":94},"8ced3b591181",[786],{"_key":787,"_type":72,"marks":788,"text":789},"8ced3b5911810",[],"This is obviously almost impossible to do from a programming language perspective. The only way you could provide more isolation is to go further into microservices—which might make sense from an architecture perspective, but not from a future-proofing one.",[],{"_key":792,"_type":68,"children":793,"markDefs":806,"style":94},"ac36c4d23440",[794,798,802],{"_key":795,"_type":72,"marks":796,"text":797},"ac36c4d234400",[],"As for data stores, no one—",{"_key":799,"_type":72,"marks":800,"text":801},"ac36c4d234401",[87],"no one",{"_key":803,"_type":72,"marks":804,"text":805},"ac36c4d234402",[],"—actually changes their database from MySQL to Postgres or vice versa. If you're using an open-source or open-standards way of interacting with your data, go all into it and don't look back. At this point, the similarities far outweigh the differences, and if you do need to switch for whatever arcane reason, you'll need a full regression suite anyway to make sure nothing else breaks.",[],{"_key":808,"_type":68,"children":809,"markDefs":823,"style":94},"98cf3a267ffd",[810,814,819],{"_key":811,"_type":72,"marks":812,"text":813},"98cf3a267ffd0",[],"(Note: This point is often used to discourage the use of ",{"_key":815,"_type":72,"marks":816,"text":818},"98cf3a267ffd1",[817],"0388b4a0f266","ORMs",{"_key":820,"_type":72,"marks":821,"text":822},"98cf3a267ffd2",[],". I think ORMs have other very good advantages, but the ability to switch data technologies is not a practical argument for their use.)",[824],{"_key":817,"_type":256,"href":825,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject%E2%80%93relational_mapping",{"_key":827,"_type":68,"children":828,"markDefs":833,"style":338},"a39132981161",[829],{"_key":830,"_type":72,"marks":831,"text":832},"a391329811610",[],"Changes to integrations",[],{"_key":835,"_type":56,"alt":12,"asset":836,"caption":305,"markDefs":12},"877087d17234",{"_ref":837,"_type":59},"image-38b61bf987087afa1dab22f831829bc1b4152804-766x40-png",{"_key":839,"_type":68,"children":840,"markDefs":853,"style":94},"415caebd8411",[841,845,849],{"_key":842,"_type":72,"marks":843,"text":844},"415caebd84110",[],"In this case, we are worried about having to change how we talk to some third-party application. These services can provide things such as metrics, tracing, alerting, logs, feature flags, object storage, deployments, etc. In this case, it's nice to have the ",{"_key":846,"_type":72,"marks":847,"text":848},"415caebd84111",[87],"freedom",{"_key":850,"_type":72,"marks":851,"text":852},"415caebd84112",[]," to change who you're doing business with based on cost, feature set, ease of use, etc.",[],{"_key":855,"_type":68,"children":856,"markDefs":861,"style":94},"e636239d5bd0",[857],{"_key":858,"_type":72,"marks":859,"text":860},"e636239d5bd00",[],"I am most inclined to ensure that we have abstraction and modularization around integrations. In many cases, these third-party applications can be fairly easily switched out for each other. Once you're providing metrics or tracing, for example, the abstractions and ideas are very close to each other regardless of which provider you've signed up with.",[],{"_key":863,"_type":68,"children":864,"markDefs":877,"style":94},"d0ebc21b0052",[865,869,873],{"_key":866,"_type":72,"marks":867,"text":868},"d0ebc21b00520",[],"What I prefer doing is building a ",{"_key":870,"_type":72,"marks":871,"text":872},"d0ebc21b00521",[87],"facade",{"_key":874,"_type":72,"marks":875,"text":876},"d0ebc21b00522",[]," around any code that needs to talk to a third-party system. Often to keep things simple, this is a wrapper around the API of whatever provider I've chosen, which exposes all the functionality necessary to operate.",[],{"_key":879,"_type":68,"children":880,"markDefs":913,"style":94},"8a4ab9d521f0",[881,885,890,894,899,904,909],{"_key":882,"_type":72,"marks":883,"text":884},"8a4ab9d521f00",[],"This facade often takes just a couple of days to build (if there isn't ",{"_key":886,"_type":72,"marks":887,"text":889},"8a4ab9d521f01",[888],"57184afd0fff","already",{"_key":891,"_type":72,"marks":892,"text":893},"8a4ab9d521f02",[]," ",{"_key":895,"_type":72,"marks":896,"text":898},"8a4ab9d521f03",[897],"2ce4076f9691","one",{"_key":900,"_type":72,"marks":901,"text":903},"8a4ab9d521f04",[902],"5d00f6b11f96","out",{"_key":905,"_type":72,"marks":906,"text":908},"8a4ab9d521f05",[907],"fa6d7ef89805"," there",{"_key":910,"_type":72,"marks":911,"text":912},"8a4ab9d521f06",[],") and can be used in whatever project needs it. I'll often add a set of team or company defaults that most closely match our most common use case so that new projects don't need to figure it out for themselves.",[914,916,918,920],{"_key":888,"_type":256,"href":915,"reference":12},"https:\u002F\u002Fgithub.com\u002Fflipp-oss\u002Fdeimos\u002F",{"_key":897,"_type":256,"href":917,"reference":12},"https:\u002F\u002Fgithub.com\u002Fmoneta-rb\u002Fmoneta",{"_key":902,"_type":256,"href":919,"reference":12},"https:\u002F\u002Fgithub.com\u002Ffog\u002Ffog",{"_key":907,"_type":256,"href":921,"reference":12},"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Fmigration\u002Fopentracing\u002F",{"_key":923,"_type":68,"children":924,"markDefs":937,"style":94},"b1724b9a6dad",[925,929,933],{"_key":926,"_type":72,"marks":927,"text":928},"b1724b9a6dad0",[],"This facade allows you to avoid ",{"_key":930,"_type":72,"marks":931,"text":932},"b1724b9a6dad1",[87],"vendor lock-in",{"_key":934,"_type":72,"marks":935,"text":936},"b1724b9a6dad2",[]," by not tying your systems to any specific provider—but the overhead is small enough that it shouldn't confuse you or your teammates.",[],{"_key":939,"_type":68,"children":940,"markDefs":953,"style":94},"d2511421300f",[941,945,949],{"_key":942,"_type":72,"marks":943,"text":944},"d2511421300f0",[],"One thing that is important is to allow you to ",{"_key":946,"_type":72,"marks":947,"text":948},"d2511421300f1",[87],"break out",{"_key":950,"_type":72,"marks":951,"text":952},"d2511421300f2",[]," of the facade by accessing the internal client or API if necessary. If you've chosen your provider because of specific features that need to be used, then you shouldn't need to tie your arm around your back to prevent you from using them. However, it means that if you do end up switching your provider, you don't have to make any changes to the \"normal\" case—you only need to look at code that accesses the internal client or API and focus your work there.",[],{"_key":955,"_type":68,"children":956,"markDefs":961,"style":94},"a4e71c83b562",[957],{"_key":958,"_type":72,"marks":959,"text":960},"a4e71c83b5620",[],"Here's a rough approximation of a feature flagging library we built for internal use:",[],{"_key":963,"_type":427,"code":964,"markDefs":12},"da820c7bb57b","module FlippFlags\n  def backend=(backend)\n    self.backend = backend\n  end\n\n  def client(config)\n    self.backend.new(config)\n  end\nend\n\nclass FlippFlags::Backend\n  def initialize(config) # this is a Ruby version of a constructor, i.e. the \"new\" keyword\n    @client = ... # initialize the actual client\n  end\n \ndef enabled?(flag, user)\n    @client.enabled?(flag, user)\nend\n\n  def internal_client\n    @client\n  end\nend\n\n# application code\nFlippFlags.backend = FlippFlags::MyFlagProvider\nclient = FlippFlags.client(my_config)\nclient.enabled?(\"flag_name\", user_object) # true or false",{"_key":966,"_type":68,"children":967,"markDefs":980,"style":94},"6373e9349a58",[968,972,976],{"_key":969,"_type":72,"marks":970,"text":971},"6373e9349a580",[],"Here you can see that the only change you need to make is to switch the ",{"_key":973,"_type":72,"marks":974,"text":975},"6373e9349a581",[427],"backend",{"_key":977,"_type":72,"marks":978,"text":979},"6373e9349a582",[]," that is passed into the library, and it seamlessly switches to a different provider—or maybe even an internal class that acts the same way! Otherwise, you use the client exactly the way you'd use the internal client, but without tying your code directly to that implementation.",[],{"_key":982,"_type":68,"children":983,"markDefs":988,"style":338},"ac0b97763cf7",[984],{"_key":985,"_type":72,"marks":986,"text":987},"ac0b97763cf70",[],"Changes to schemas",[],{"_key":990,"_type":56,"alt":12,"asset":991,"caption":305,"markDefs":12},"bb6dfabf9373",{"_ref":992,"_type":59},"image-c8989095e3501711cfa4627237ce1f518abdfcdb-766x40-png",{"_key":994,"_type":68,"children":995,"markDefs":1000,"style":94},"2a5b34c85406",[996],{"_key":997,"_type":72,"marks":998,"text":999},"2a5b34c854060",[],"The shape of our data reflects our understanding of it. As we gain more understanding, we often want to change its shape. This could mean adding or removing fields, changing field types, default values, etc.",[],{"_key":1002,"_type":68,"children":1003,"markDefs":1026,"style":94},"eb7a5385fe6f",[1004,1008,1013,1017,1022],{"_key":1005,"_type":72,"marks":1006,"text":1007},"eb7a5385fe6f0",[],"Making our data schemas backwards and forwards compatible is possible by following some simple rules. Technologies such as ",{"_key":1009,"_type":72,"marks":1010,"text":1012},"eb7a5385fe6f1",[1011],"e89da2b7f528","Avro",{"_key":1014,"_type":72,"marks":1015,"text":1016},"eb7a5385fe6f2",[]," and ",{"_key":1018,"_type":72,"marks":1019,"text":1021},"eb7a5385fe6f3",[1020],"54cba48f0c7b","Protobuf",{"_key":1023,"_type":72,"marks":1024,"text":1025},"eb7a5385fe6f4",[]," specify those rules and have built them into their tooling. If you're not using those tools, though, you can \"soft-enforce\" similar rules whenever you change your own schema to ensure it's unlikely that your changes will break whatever depends on your data.",[1027,1029],{"_key":1011,"_type":256,"href":1028,"reference":12},"https:\u002F\u002Fdocs.confluent.io\u002Fplatform\u002Fcurrent\u002Fschema-registry\u002Favro.html#summary",{"_key":1020,"_type":256,"href":1030,"reference":12},"https:\u002F\u002Fdocs.buf.build\u002Fbreaking\u002Frules",{"_key":1032,"_type":68,"children":1033,"markDefs":1038,"style":94},"3a938a17b67e",[1034],{"_key":1035,"_type":72,"marks":1036,"text":1037},"3a938a17b67e0",[],"Having said that, there are cases where you simply can't follow those rules—and that's okay! This is when you specify a migration path to go from the old data to the new data. But the rules make it so you shouldn't need to follow that onerous process for every single data change you go through.",[],{"_key":1040,"_type":68,"children":1041,"markDefs":1046,"style":119},"c71bbd4d8528",[1042],{"_key":1043,"_type":72,"marks":1044,"text":1045},"c71bbd4d85280",[],"Conclusion",[],{"_key":1048,"_type":68,"children":1049,"markDefs":1071,"style":94},"553c44923f51",[1050,1054,1058,1062,1067],{"_key":1051,"_type":72,"marks":1052,"text":1053},"553c44923f510",[],"Future-proofing is not necessarily a ",{"_key":1055,"_type":72,"marks":1056,"text":1057},"553c44923f511",[87],"goal",{"_key":1059,"_type":72,"marks":1060,"text":1061},"553c44923f512",[]," of software development so much as one of the many \"",{"_key":1063,"_type":72,"marks":1064,"text":1066},"553c44923f513",[1065],"ac1464f567d3","ilities",{"_key":1068,"_type":72,"marks":1069,"text":1070},"553c44923f514",[],"\" that affect your design. Overcorrecting on this spectrum can lead to unnecessary work, tech debt, and confusing abstractions. Finding the sweet spot, though, can save you time and effort down the road.",[1072],{"_key":1065,"_type":256,"href":1073,"reference":12},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FList_of_system_quality_attributes",true,"2022\u002F05\u002F19","While the future may be a mystery, you can design software to accommodate future changes. But how much future-proofing gets in the way of good design? ",{"_type":56,"asset":1078},{"_ref":1079,"_type":59},"image-4b21b1a13f3a921852af21c666c6d2d8631d580b-2560x1344-jpg",{"code":1081,"language":1082},"\u003C!-- wp:paragraph -->\n\u003Cp>The concept of \u003Cstrong>future-proofing\u003C\u002Fstrong> your code is one that perennially pops up in conversations about software. It sounds magical—who \u003Cem>wouldn't\u003C\u002Fem> want to make your code impervious to the future?\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Of course, the reality is much less rosy and much messier.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In this article, I'm going to discuss what I think people mean by \"future-proofing,\" how you might be able to accomplish it, and when and why it might be a bad choice.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-how-does-one-proof-one-s-future\">How does one \"proof\" one's \"future?\"\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You can think of future-proofing more accurately as \u003Cstrong>change-proofing\u003C\u002Fstrong>. We can define it as:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cem>\"Making a design, architecture, or programming decision that allows for future changes to be easier to manage, take less time or result in fewer changes to the overall code.\"\u003C\u002Fem>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>These changes can fall into a number of different categories:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:list -->\n\u003Cul>\u003Cli>Changes to \u003Cstrong>scale\u003C\u002Fstrong> - the project has to do more of the things it's doing, whether that means handling more traffic or work items, processing them faster, etc.\u003C\u002Fli>\u003Cli>Changes to \u003Cstrong>requirements\u003C\u002Fstrong> - new information has entered the business (or has entered the engineering team from the business) and now the system needs to change to accommodate them.\u003C\u002Fli>\u003Cli>Changes to \u003Cstrong>technology stack\u003C\u002Fstrong> - switching to a different data store or programming language, for example.\u003C\u002Fli>\u003Cli>Changes to \u003Cstrong>integrations\u003C\u002Fstrong> - the project needs to talk to a new third-party application, either on top of or instead of an existing one.\u003C\u002Fli>\u003Cli>Changes to \u003Cstrong>schemas\u003C\u002Fstrong> - we want to change the fields that define our data objects.\u003C\u002Fli>\u003C\u002Ful>\n\u003C!-- \u002Fwp:list -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-should-i-stay-or-should-i-grow\">Should I stay or should I grow?\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The main issue with future-proofing is that it runs slap-bang into one of the central tenets of software engineering: YAGNI, or \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FYou_aren%27t_gonna_need_it\">\u003Cstrong>You Ain't Gonna Need It\u003C\u002Fstrong>\u003C\u002Fa>.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This principle states that until you actually know you're going to have a change, you shouldn’t code your software in a way that anticipates that change. Violating this principle results in bloat, confusing and unnecessary abstractions that make the code harder to understand, and often mounds of tech debt.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>However, if you never cast your eyes to the future, you really can run into issues where just a little extra bit of work upfront could have saved you months of it down the road.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>So... should you future-proof your code? The answer, as with practically everything in software, is, \"\u003Ca href=\"https:\u002F\u002Fmedium.com\u002Fswlh\u002Fthe-golden-rule-of-software-engineering-9faaaab85e78\">it depends\u003C\u002Fa>.\"\u003Cbr>\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I see this as a spectrum of sorts:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh5.googleusercontent.com\u002FxytLMy1usGAxZhztFox_Ka4Vt_k3HGK63GXYxkBpiPynrwGiypQFKtnyPSlVr1v5KibijFqH55QAB4zWj-rn0NouqkIM09jjIqriRnHWiJLJLgs5nCCNbQ8boGkfkLWmGxRHMAJPOFWDGR5W4w\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Every situation is unique, of course. But some kinds of changes are much less likely to deserve future-proofing, while in others, the extra work is more likely to pay off and (just as importantly) not result in significant downsides.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-future-proofing-strategies\">Future-proofing strategies\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In general, any time we want to protect ourselves from future changes, we would engage in one of two strategies.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-modularization\">Modularization\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cstrong>Modularization\u003C\u002Fstrong> is the act of splitting your code up into smaller chunks. Every piece of software has some level of modularization—otherwise, your code would consist of a single enormous file that had nothing but unrolled functions and primitive types. (These programs do exist, but no one in their right mind wants anything to do with them.)&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Increasing modularization allows you to \u003Cstrong>isolate\u003C\u002Fstrong> your changes to a single module, and\u002For to more easily \u003Cstrong>swap\u003C\u002Fstrong> modules out for each other.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You can also have modularization at different levels. Extracting code into a function is a simple act of modularization that rarely has a downside until you're at the point where your functions are so small and numerous that they're hard to keep track of. However, you can extract things into a class, a sub-application, a microservice, or even a separate cluster; and at each level, while the isolation and flexibility goes up, so does the cognitive and administrative overhead of managing your different components.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-abstraction\">Abstraction\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cstrong>Abstraction\u003C\u002Fstrong> is the mental model you have of the parts of your code. A module, function, class, etc. with \u003Cstrong>low abstraction\u003C\u002Fstrong> (or higher \u003Cstrong>specificity\u003C\u002Fstrong>) more closely models the action it takes or object it represents. A thing with \u003Cstrong>higher abstraction\u003C\u002Fstrong> brings your reasoning up a level. For example, instead of your code working with \u003Ccode>Cars\u003C\u002Fcode>, it could work with \u003Ccode>Vehicles\u003C\u002Fcode>. That way, if you ever need to start handling \u003Ccode>Motorcycles\u003C\u002Fcode>, the change necessary to do so is mitigated by the fact that you were never really talking about \u003Ccode>Cars\u003C\u002Fcode> to begin with.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The higher you go with abstraction, the more flexible you make your code, but also the harder it is to understand. Everyone knows what a \u003Ccode>Car\u003C\u002Fcode> is, but when you start working with \u003Ccode>Vehicles\u003C\u002Fcode>, it becomes more difficult to work with \u003Ccode>Car\u003C\u002Fcode>-specific things like booster seats and steering wheels. And if you keep going higher up the abstraction tree, you might find yourself coding around \u003Ccode>Transportation\u003C\u002Fcode>, \u003Ccode>ManufacturedGoods\u003C\u002Fcode>, even plain old \u003Ccode>Objects\u003C\u002Fcode>, and have a very hard time figuring out how to fill up your tank.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-types-of-changes\">Types of changes\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Lets go through each of the types of changes I listed above. For each, I’m recommending where on the spectrum your future-proofing efforts might lie. In general, I will be applying these suggestions for when you have \u003Cstrong>no current indication that the change will happen\u003C\u002Fstrong>. As the change becomes more likely, you'd move further towards the left side of the line.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-changes-to-scale\">Changes to scale\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh6.googleusercontent.com\u002FPc3UIOeLmmmkss00zHoTcDIFq6SEKWSuH-BExgGfleUh_iTEo3L8kPuMpKI7L6LNda5nPXn1g-B-0Y1q2NEazw7kDl4g3RbFWvnOnxA5io2C3iyRJJNDd88lZpstloPybGkzDwnQYetGZtUfaA\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Making your code and architecture robust and able to handle whatever you throw at it is a core facet of design. However, part of that architecture and design has to be what kind of traffic you expect it to have. The higher scale you have to handle, in general, the more complex your architecture needs to be.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Making a simple app that's designed to be used by a handful of internal users could be done via a Rails or Django application with a minimum of JavaScript, for example. But if you need to be able to handle thousands or millions of concurrent users, that's just not going to cut it. You're going to need lightning-fast services or micro-services, horizontal scaling and auto-scaling, and probably a more responsive front-end and more tailored and complex caching mechanisms.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Over-architecting all of this upfront when you have no anticipation of your app actually scaling to that size is flagrant YAGNI. In fact, building your system deliberately for your current scale \u003Cem>even if you know you may have to rewrite it when the scale changes\u003C\u002Fem> is a perfectly valid strategy, sometimes called \u003Ca href=\"https:\u002F\u002Fstackoverflow.blog\u002F2021\u002F03\u002F01\u002Fsacrificial-architecture-learning-from-abandoned-systems\u002F\">sacrificial architecture\u003C\u002Fa>.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Having said that, there are \u003Cem>some\u003C\u002Fem> scaling considerations you should take into account. Leaving in lots of \u003Ca href=\"https:\u002F\u002Fstackoverflow.com\u002Fquestions\u002F97197\u002Fwhat-is-the-n1-selects-problem-in-orm-object-relational-mapping\">N+1 queries\u003C\u002Fa> or having unnecessarily large or frequent requests is bad engineering practice all around and will leave a bad taste in your users' mouths—regardless of how many or few they are.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-changes-to-requirements\">Changes to requirements\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh5.googleusercontent.com\u002FWBH2G8Jpi5XX-D_z_9vZVFjE14zTMUW0MvO2aOVt451X4F3Yek2O_WBnpT4ZwteuDU6DYkY2XHaOZ_HE6_YpaI2s2JvUr6wk6joc-ZJIHCHA-2saWrmv5NTq1Yzc7F0YwaJy0b_j4U6Ukx9ymQ\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This case (where your code has to change or add to its behavior) is the hardest one to guess at, as well as the one most likely to happen.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is a case where general modularization can help you, but I'd recommend against trying to prematurely add abstraction to your code.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In particular, if we're talking about \u003Cem>business rules\u003C\u002Fem>, isolating the rules themselves in a way that makes them easy to test and change is an overall good practice.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>As a simple example, here's some code working with a product that has some rules around what a valid product can be considered:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>product.save if product.price >= 0 &amp;&amp; !product.name.nil?\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Products might be saved from many different places in your application. Rather than the calling code checking these business rules, we can extract them to their own method:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>class Product\n  def valid?\n    self.price >= 0 &amp;&amp; !self.name.nil?\n  end\nend\nproduct.save if product.valid?\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We can even go further and have a separate class or module that handles this check:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>module ProductValidator\n  def self.valid?(product)\n    product.price >= 0 &amp;&amp; !product.price.nil?\n  end\nend\nproduct.save if ProductValidator.valid?(product)\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>These are relatively minor changes and keep your code clean and easily testable, and can isolate further changes of this sort to the one place.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The key words are \u003Cem>\"of this sort\"\u003C\u002Fem>, however. If the requirements changes involve completely changing how the behavior of a feature acts, or adding new features, that's where you want to start putting your foot down more firmly on the YAGNI side of things and only building what you actually know.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Let's imagine that your product can be purchased:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>class Product\n  def purchase\n    # contact the purchase service and complete transaction\n  end\nend\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Wait a minute, you reason. We might eventually expand into not only buying products but also selling them! Maybe renting them? We should abstract this out:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>class Product\n  def perform_action\n  end\nend\n\nclass PurchasableProduct\n  def perform_action\n    # contact the purchase service and complete transaction\n  end\nend\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is classic YAGNI over-abstraction. People reading your code won't see you purchasing a product, you're \"performing an action\" on something that \u003Cem>resolves\u003C\u002Fem> to purchasing.&nbsp;\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You have no reason to believe that your business actually will expand into further operations. You've introduced a level of abstraction that removes your code one extra step from what's actually happening and what's being modeled.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-changes-to-technology-stack\">Changes to technology stack\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh5.googleusercontent.com\u002FsRWM8cJffFf8ToLgNAuqCdmmM8oA59N0gDi3SHkIJiOjgLARyXQlie6gC0bN7ey0ksvnFqSbkN2gajZsSZ81SFs7x4xe3DoQktgQU9eO5ms-2fhkF1OP0u5Bnb4fbHpzYTfjS-rwwI9UVROXeQ\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I'll come straight out and say it—there is just no good reason to build your app in a way that you plan to eventually change your basic technology stack.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is obviously almost impossible to do from a programming language perspective. The only way you could provide more isolation is to go further into microservices—which might make sense from an architecture perspective, but not from a future-proofing one.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>As for data stores, no one—\u003Cem>no one\u003C\u002Fem>—actually changes their database from MySQL to Postgres or vice versa. If you're using an open-source or open-standards way of interacting with your data, go all into it and don't look back. At this point, the similarities far outweigh the differences, and if you do need to switch for whatever arcane reason, you'll need a full regression suite anyway to make sure nothing else breaks.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>(Note: This point is often used to discourage the use of \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject%E2%80%93relational_mapping\">ORMs\u003C\u002Fa>. I think ORMs have other very good advantages, but the ability to switch data technologies is not a practical argument for their use.)\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-changes-to-integrations\">Changes to integrations\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh4.googleusercontent.com\u002F9Rsd4CPkbx_VfKauh_vR-6Rg2YgSG8K-tefJK5rtH6VPxrP2gSS8YmGD_mV3AdZ6hdBa6u1lJ-jkNjhswRWzyBbfnSg1ny_xO7UQnuHvxKzhjmAa6PgznLjYFPGnw6M5DZFCAa3jrebunisPjQ\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In this case, we are worried about having to change how we talk to some third-party application. These services can provide things such as metrics, tracing, alerting, logs, feature flags, object storage, deployments, etc. In this case, it's nice to have the \u003Cem>freedom\u003C\u002Fem> to change who you're doing business with based on cost, feature set, ease of use, etc.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I am most inclined to ensure that we have abstraction and modularization around integrations. In many cases, these third-party applications can be fairly easily switched out for each other. Once you're providing metrics or tracing, for example, the abstractions and ideas are very close to each other regardless of which provider you've signed up with.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>What I prefer doing is building a \u003Cem>facade\u003C\u002Fem> around any code that needs to talk to a third-party system. Often to keep things simple, this is a wrapper around the API of whatever provider I've chosen, which exposes all the functionality necessary to operate.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This facade often takes just a couple of days to build (if there isn't \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fflipp-oss\u002Fdeimos\u002F\">already\u003C\u002Fa> \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmoneta-rb\u002Fmoneta\">one\u003C\u002Fa>\u003Ca href=\"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Fmigration\u002Fopentracing\u002F\"> \u003C\u002Fa>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ffog\u002Ffog\">out\u003C\u002Fa>\u003Ca href=\"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Fmigration\u002Fopentracing\u002F\"> there\u003C\u002Fa>) and can be used in whatever project needs it. I'll often add a set of team or company defaults that most closely match our most common use case so that new projects don't need to figure it out for themselves.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This facade allows you to avoid \u003Cem>vendor lock-in\u003C\u002Fem> by not tying your systems to any specific provider—but the overhead is small enough that it shouldn't confuse you or your teammates.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>One thing that is important is to allow you to \u003Cem>break out\u003C\u002Fem> of the facade by accessing the internal client or API if necessary. If you've chosen your provider because of specific features that need to be used, then you shouldn't need to tie your arm around your back to prevent you from using them. However, it means that if you do end up switching your provider, you don't have to make any changes to the \"normal\" case—you only need to look at code that accesses the internal client or API and focus your work there.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Here's a rough approximation of a feature flagging library we built for internal use:\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>module FlippFlags\n  def backend=(backend)\n    self.backend = backend\n  end\n\n  def client(config)\n    self.backend.new(config)\n  end\nend\n\nclass FlippFlags::Backend\n  def initialize(config) # this is a Ruby version of a constructor, i.e. the \"new\" keyword\n    @client = ... # initialize the actual client\n  end\n \ndef enabled?(flag, user)\n    @client.enabled?(flag, user)\nend\n\n  def internal_client\n    @client\n  end\nend\n\n# application code\nFlippFlags.backend = FlippFlags::MyFlagProvider\nclient = FlippFlags.client(my_config)\nclient.enabled?(\"flag_name\", user_object) # true or false\u003C\u002Fcode>\u003C\u002Fpre>\n\u003C!-- \u002Fwp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Here you can see that the only change you need to make is to switch the \u003Ccode>backend\u003C\u002Fcode> that is passed into the library, and it seamlessly switches to a different provider—or maybe even an internal class that acts the same way! Otherwise, you use the client exactly the way you'd use the internal client, but without tying your code directly to that implementation.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":3} -->\n\u003Ch3 id=\"h-changes-to-schemas\">Changes to schemas\u003C\u002Fh3>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https:\u002F\u002Flh5.googleusercontent.com\u002FyYC4ZtPvXZE7s_t5NuR_7_fLlIeXn4r46Ye4SGP3d6WA9CrZ-36BoEwl-Lso3x5qjGpQ2igcZg2bPKmQXHCiB2UFIzH78Sj0dAnYJCihRm_8G_jg_Y7qi1WzwTFgw1f--R7DloJSTDGGeFHFPA\" alt=\"\"\u002F>\u003C\u002Ffigure>\n\u003C!-- \u002Fwp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The shape of our data reflects our understanding of it. As we gain more understanding, we often want to change its shape. This could mean adding or removing fields, changing field types, default values, etc.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Making our data schemas backwards and forwards compatible is possible by following some simple rules. Technologies such as \u003Ca href=\"https:\u002F\u002Fdocs.confluent.io\u002Fplatform\u002Fcurrent\u002Fschema-registry\u002Favro.html#summary\">Avro\u003C\u002Fa> and \u003Ca href=\"https:\u002F\u002Fdocs.buf.build\u002Fbreaking\u002Frules\">Protobuf\u003C\u002Fa> specify those rules and have built them into their tooling. If you're not using those tools, though, you can \"soft-enforce\" similar rules whenever you change your own schema to ensure it's unlikely that your changes will break whatever depends on your data.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Having said that, there are cases where you simply can't follow those rules—and that's okay! This is when you specify a migration path to go from the old data to the new data. But the rules make it so you shouldn't need to follow that onerous process for every single data change you go through.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->\n\n\u003C!-- wp:heading -->\n\u003Ch2 id=\"h-conclusion\">Conclusion\u003C\u002Fh2>\n\u003C!-- \u002Fwp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Future-proofing is not necessarily a \u003Cem>goal\u003C\u002Fem> of software development so much as one of the many \"\u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FList_of_system_quality_attributes\">ilities\u003C\u002Fa>\" that affect your design. Overcorrecting on this spectrum can lead to unnecessary work, tech debt, and confusing abstractions. Finding the sweet spot, though, can save you time and effort down the road.\u003C\u002Fp>\n\u003C!-- \u002Fwp:paragraph -->","html","2022-05-19T13:51:45.000Z",{"current":1085},"crystal-balls-and-clairvoyance-future-proofing-in-a-world-of-inevitable-change",[1087,1095,1099,1104],{"_createdAt":1088,"_id":1089,"_rev":1090,"_type":1091,"_updatedAt":1088,"slug":1092,"title":1094},"2023-05-23T16:43:21Z","wp-tagcat-code-for-a-living","9HpbCsT2tq0xwozQfkc4ih","blogTag",{"current":1093},"code-for-a-living","Code for a Living",{"_createdAt":1088,"_id":1096,"_rev":1090,"_type":1091,"_updatedAt":1088,"slug":1097,"title":1098},"wp-tagcat-future-proof",{"current":1098},"future-proof",{"_createdAt":1088,"_id":1100,"_rev":1090,"_type":1091,"_updatedAt":1088,"slug":1101,"title":1103},"wp-tagcat-software-architecture",{"current":1102},"software-architecture","software architecture",{"_createdAt":1088,"_id":1105,"_rev":1090,"_type":1091,"_updatedAt":1088,"slug":1106,"title":1108},"wp-tagcat-software-engineering",{"current":1107},"software-engineering","software engineering","Crystal balls and clairvoyance: Future proofing in a world of inevitable change",[1111,1117,1123,1129],{"_id":1112,"publishedAt":1113,"slug":1114,"sponsored":12,"title":1116},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":1115},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":1118,"publishedAt":1119,"slug":1120,"sponsored":12,"title":1122},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":1121},"your-ai-shipped-a-backend-that-boots-that-is-the-whole-problem","Your AI shipped a backend that boots. That is the whole problem.",{"_id":1124,"publishedAt":1125,"slug":1126,"sponsored":12,"title":1128},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":1127},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"_id":1130,"publishedAt":1131,"slug":1132,"sponsored":12,"title":1134},"30b995f7-7cb9-4dd8-bf71-d0685940a32b","2026-06-19T14:00:00.000Z",{"_type":10,"current":1133},"dispatches-from-o-reilly-from-capabilities-to-responsibilities","Dispatches from O'Reilly: From capabilities to responsibilities",{"data":1136,"sourceMap":-1},{"count":1137,"lastTimestamp":1138},4,"2023-05-25T09:47:51Z"]