Loading…
Topic
Refactoring
36 posts about Refactoring. Every summary links to the original.
- 36 posts
- 13 companies
The Good Stuff
Paid picks we use ourselves. Links carry a referral code — a subscription through one earns us a commission at no extra cost to you.
Newest first
Posts
How we saved 100 terabytes of memory by optimizing 1.1.1.1’s DNS cache
Cloudflare's Big Pineapple platform, which powers 1.1.1.1 and other DNS services, stores over 250 billion cache entries simultaneously. To decrease fleet-wide memory consumption, engineers implemented five changes to in-memory cache data structures. Key modifications included swapping growable types like Vec and String for Box slices to eliminate capacity overhead, combining distinct record sections into a single list indexed by offsets, omitting redundant owner domain names, and serializing record bytes into contiguous buffers. In benchmarks, these optimizations decreased per-entry memory footprints by 56% while improving cache lookup latency by 19% and insert throughput by 43%. Across production instances, the rollout reduced working-set memory by approximately 100 terabytes.
Ready for Go 1.27 on Day One
GoLand 2026.2 introduces day-one support for Go 1.27 language features, tooling improvements, and profiling capabilities. The Go 1.27 release adds generic methods, promoted field names in struct composite literals, improved function type inference, and a dedicated profile for detecting goroutine leaks. To assist with code maintenance, GoLand integrates official go fix modernizers directly into editor inspections, the Problems tool window, and optional pre-commit checks. Developers can capture, visualize, and analyze goroutine leak profiles alongside existing CPU, memory, and mutex profiles within the IDE. Additionally, updated Modern Go Code Guidelines provide AI coding agents with Go 1.27 context and API changes aligned with the version specified in go.mod.
Migrating a Large Flow Monorepo to TypeScript
Yelp migrated a 1.4-million-line JavaScript monorepo from Flow to TypeScript after community support and package ecosystem tooling shifted toward TypeScript. To maintain continuous type safety without code freezes, engineers converted the codebase package by package starting at the deepest internal dependencies. The toolchain used flowts for code conversion and flowgen to turn generated TypeScript declaration files back into Flow-compatible headers for downstream consumers. Webcore dogfooded the tooling across 90 packages, automated tracking through internal metrics, and assisted feature teams with bottleneck conversions. Completed in February 2026, the migration increased overall type coverage from 83.15% to 96.44% and unlocked TypeScript-only tooling.
The cost of saying yes has changed
Generating initial code patches has become significantly cheaper with AI agents, shifting the primary expense of small feature requests from writing code to debating scope in meetings. Teams can use agent-generated patches as diagnostic probes rather than final deliverables, turning abstract scope debates into concrete artifacts that reveal true system touchpoints and risks. However, low generation costs do not translate to low ownership costs, as changes touching areas like authorization, compliance, or public contracts still demand significant human review. Constrained attempts allow engineers to price uncertainty quickly and shift scope discipline from pre-implementation speculation to evidence-based code review.
Formatting an entire 25 million line codebase overnight: the rubyfmt story
Stripe’s 25-million-line Ruby codebase needed a true autoformatter; before rubyfmt, no industry-wide Ruby formatter existed, and earlier tools had crashed on its files. rubyfmt began as a zero-configuration open-source project motivated by formatting disputes, Ruby’s slow startup, and the uncertainty faced by engineers arriving from other languages. To meet a 100ms target, it initially used a hand-authored C launcher and later moved formatting into Rust, with ripper parse trees first passing through JSON and eventually being deserialized directly from Ruby VALUE objects. Stripe adopted per-file opt-in, expanded coverage cautiously, and formatted the codebase on a Saturday in 2024; the excerpt reports that all 42-million-line Ruby codebase is now formatted. In 2025, migrating from ripper to Prism removed the need to link a Ruby VM and made rubyfmt smaller and dramatically faster.
How Slack Rebuilt Notifications 📣
Slack redesigned its legacy notification architecture to address user confusion, conflicting desktop and mobile preference systems, and tight coupling between notification content and push delivery. The engineering team unified four separate preference models into a single framework with three channel options: All new posts, Mentions, and Mute. To ensure safe rollback and backwards compatibility without direct database mutations, Slack implemented a read-time translation strategy alongside a new desktop_push_enabled preference and auto-saving cross-platform React UI components. This decoupling of activity from push delivery resulted in a fivefold sustained increase in settings engagement, reduced reliance on per-channel overrides, and eliminated synchronization mismatches across mobile and desktop clients.
The Cost of Not Knowing MongoDB, Part 3: appV6R0 to appV6R4
To alleviate a disk throughput bottleneck on a MongoDB server, this optimization pass focuses on shrinking document sizes across application revisions appV6R0 through appV6R4. The implementation replaces an array of items with a dynamic schema document, using date segments such as days or combined months and days as field names to store status totals. Redundant date components already encoded in the binary _id field are omitted from internal document keys. Bulk upsert operations rely on updateOne with the $inc operator to update matching date keys or create documents when missing. Aggregation pipelines process reports using the $objectToArray and $reduce operations to calculate totals across date intervals while preserving a single index on _id.
Migrating to React Native's New Architecture
Shopify successfully migrated two largest apps, Shopify Mobile and Shopify Point of Sale, to React Native’s New Architecture while continuing weekly releases for millions of merchants. The strategy prioritized minimal code changes, dual-architecture compatibility, and performance and stability parity, with TopHat generating both architecture builds for every pull request. The team upgraded React Native first, updated or removed incompatible dependencies, and changed only native modules that failed under the new architecture rather than migrating all 40-plus modules to TurboModules. Migration issues included state batching, UIManager-related blank screens, shadow-tree manipulation, view flattening, and main-thread legacy module initialization; the reported outcome was maintained development velocity with no feature-development disruption, followed by planned optimization work.
From Intern Project to Production: How I Shipped the Draw Tool for Canva's Present Mode
Canva engineers developed and shipped a real-time drawing tool for presentation mode after user feedback highlighted it as a highly requested feature. Integrating the existing editor-bound draw functionality into presentations required resolving tight package coupling, dual-window scaling mismatches, and performance regressions. To overcome architectural boundaries, core draw logic was extracted into a shared common package using interface abstractions. Coordinate normalization resolved positioning and scaling differences across presenter and audience views, while code-splitting deferred loading the core engine until activation. As a result, presentation load time regression dropped from 7% to 0.24%, and the feature reached over 470,000 monthly active users in production.
How We Decomposed Tinder’s Monolith
Tinder faced significant agility and build performance challenges caused by an iOS codebase monolith containing over 1,000 files and 150,000 lines of code. Manual extraction efforts risked creating massive, unmanageable pull requests that would require constant rebasing against the main branch. To systematically decompose the target into Swift sub-modules, the team mapped declarations and references via the Swift compiler into a directed graph. They iteratively extracted leaf nodes with an in-degree of zero across sequential phases and automated common code adjustments, including module dependencies, imports, access control levels, and dependency injection. The automated decomposition completed in under six months with zero P0 incidents, reducing monolith build times by 78% and disallowing future additions to the monolith target.
A Packwerk Retrospective
Shopify’s retrospective examines Packwerk, a Ruby gem released in September 2020 to enforce package boundaries and help modularize Rails applications. It describes the tool as static analysis that checks constant references against a declared, non-circular dependency graph, with todo files allowing existing violations to be recorded while new ones are blocked. The authors recount how privacy checks shifted attention toward API design, introduced awkward file organization, and were removed in version 3.0. They also explain that Packwerk cannot determine whether package boundaries or violation fixes reflect runtime coupling, so semantic grouping can mislead developers and add harmful indirection. Shopify ultimately treats running code as the strongest progress signal and retains Packwerk mainly to prevent new dependencies at the application’s base layer.
How Migrating from Vanilla Redux to Redux Toolkit Improved State Management in Shopify POS
Shopify’s React Native Point of Sale team migrated its production state management from Vanilla Redux to Redux Toolkit (RTK) after developers found boilerplate and debugging difficult. They rejected a full rewrite in favor of incremental reducer-by-reducer conversion, allowing Vanilla reducers and RTK slices to coexist and limiting rollback and destabilization risk. Most reducers adopted RTK’s mutable syntax and generated action creators, while asynchronous logic retained a hybrid thunk approach because RTK Query did not support GraphQL and wholesale use of pending and fulfilled actions would split logic between thunks and reducers. The roughly three-month migration deleted 3,500 lines of boilerplate and was released gradually to production. The team also identified explicit TypeScript typing for dispatch, selector, and store hooks, plus dispatch().unwrap() for awaited calls, as important details, while atypical reducers were deferred to separate projects.
What Being a Staff Developer Means at Shopify
The post defines a staff developer at Shopify as an individual contributor whose scope and seniority can match a manager’s, while emphasizing that the role is a form of leadership rather than simply a more senior senior developer. Technical excellence comes from varied, challenging project experience, including large-scale systems, refactors, technology shifts, mistakes, and learning to choose appropriate solutions under constraints. The role also pairs with a team manager, translating product and business objectives into technical proposals, judging scope and infrastructure needs, finding recurring product problems, and saying no when effort does not match return. Its impact is measured partly through enabling others via reviews, mentorship, sponsorship, and support, leading to an adaptable leader comfortable with ambiguity whose responsibilities evolve with company needs.
Responsibly mid-size: What Matters, Suffers
Moving into a mid-size company requires adopting processes and attitudes piecemeal rather than suddenly operating like a large enterprise, while preserving the ability to deliver customer value. The post challenges “clean code” as a universal standard, arguing from Sandi Metz’s discussion of churn and semantic complexity that important, frequently changed files are often large and complicated, making “bad code” an emergent feature of successful systems. It also uses Joel Spolsky’s case against rewrites to explain that ugly code may contain accumulated fixes for real bugs and operating conditions. The resulting advice is to distinguish code that manages responsibilities from code that does not, develop engineering judgment, and avoid letting aesthetic reactions damage collaboration, team cohesion, or delivery.
The Case Against Monkey Patching, From a Rails Core Team Member
The post argues that Ruby monkey patches—global changes to existing objects or libraries—should be used sparingly because they are brittle, dangerous, and often unnecessary. They can make Rails and Ruby upgrades harder by depending on private APIs, leave applications exposed to vulnerabilities if security fixes are not mirrored, and accumulate poorly documented technical debt. One Shopify example, activerecord-pedant-adapter, changes Active Record’s MySQL2Adapter so database connections report MySQL query warnings; upstreaming that behavior into Rails allowed the gem to be archived and the patch removed. The recommended alternative is to investigate whether an upgrade or documentation resolves the issue, fix genuine defects upstream, and, when patching is unavoidable, document and test it while maintaining a removal plan.
The 25 Percent Rule for Tackling Technical Debt
Technical debt is framed as four categories that need different responses: yearly debt may involve rewrites or gradual replacement, monthly debt requires team-sized planning, weekly debt can become a sprint card, and daily debt is code tidying during ordinary work. The article cautions that some apparent yearly debt reflects growth or changing markets rather than a past mistake, while smaller debt can be deferred only if it is recorded and scheduled. The proposed 25-percent allocation gives 10% to daily improvements, 10% to weekly work, and 5% to planning monthly and yearly debt, with the latter focused on prioritization rather than implementation. Its conclusion is that sustainable remediation depends on team culture: engineers need permission and encouragement to refactor, teams need space to schedule broader work, and leaders need to weigh costs and tradeoffs.
How to Write Code Without Having to Read It
The article argues that developers can minimize code reading when fixing bugs or changing features by focusing on information not already checked by tools. It recommends locating a user-visible entrypoint, then making a deliberately breaking edit and climbing the callstack through build errors rather than reading each caller. Strict types, tagged unions, exhaustive switches, linters, and unit tests expose incompatible inputs, missing cases, deprecated patterns, and affected consumers; pure functions separated from side-effect code keep tests simple and informative. After the tooling-driven passes, developers manually confirm behavior, revisiting the design when runtime issues remain and making future changes more "unread"-able.
Focus On Behavior, Not State, for a More Maintainable Codebase
Shopify’s work adding pre-orders and “try before you buy” to its Purchase Options APIs exposed code that coupled selling plans too tightly to subscriptions, making future purchase models harder to add. The team reframed the domain around orders and behaviors, treating subscriptions as recurring orders and distinguishing recurring or deferred delivery and billing where those properties mattered. It introduced a wrapper around selling-plan Active Record objects, replaced those objects outside their defining component, and exposed methods such as whether deliveries are recurring while keeping policy details encapsulated. Because platform behavior and merchant reporting need different concepts, the team also added a translation table populated at order creation, with calculated or merchant-provided labels and optional trait records; the maintenance cost isolates reporting semantics from platform and data-team logic.
Our Favorite Moments From RailsConf 2022
RailsConf 2022 returned to an in-person format at Portland’s Oregon Convention Center after a two-year gap, with three days of keynotes, tutorials, and talks on technical and workplace topics. The 16th edition announced three new Rails Core team members, including Shopify’s Jean Boussier, and two new committers, while recurring themes included company investment in Rails, security, Active Records, “build vs buy,” diversity, and burnout. Shopify’s 15 Rails speakers contributed sessions and keynotes, and the recap gathers highlights on open-source staffing, long-term maintenance, developer wellbeing, Rails upgrades, supply-chain attacks, and modularization for “Big Rails.” Specific examples included regexp DOS risks, typosquatting and combosquatting, Rails upgrade automation, Ruby Archaeology, and the history of women in computer science. Because concurrent sessions and hallway conversations limited attendance, a virtual home edition and recordings were scheduled to make more talks available afterward.
Our Experience Porting the YJIT Ruby Compiler to Rust
Shopify’s YJIT team moved the CRuby JIT compiler from C99 to Rust after finding that C’s limited complexity-management tools made the growing project difficult to maintain. The port had to preserve a non-trivial interface with CRuby, including parsing bytecode and manipulating Ruby’s primitive types, while compiling the Rust project into a static library linked through CRuby’s makefile. The team translated functions and structs largely one by one, retaining the original architecture while using Rust features such as pattern matching, macros, Cargo features, and borrow checking. Completed in three months, the port was judged more maintainable and pleasant to work with, although the team identified challenges around C interoperability, unsafe code, tooling, ecosystem maturity, and limited use of idiomatic Rust.
Related topics
All topics →Weekly digest
The best engineering reads, once a week.