今日已更新 302 条资讯 | 累计 37551 条内容
关于我们

标签:#dev

找到 4363 篇相关文章

AI 资讯

How to Add AI to Your Existing SaaS Application: A Practical Guide for 2026

"Should we add AI to our product?" isn't really the right question anymore. Most SaaS founders and product teams have moved past whether to add AI and are stuck on how — how to do it without a six-month rebuild, a runaway API bill, or a feature that looks impressive in a demo but nobody actually uses. At Softication Technology Pvt. Ltd., we've worked with SaaS teams integrating AI into products ranging from CRMs to internal tooling to customer support platforms. This guide lays out the practical, engineering-first approach we use — the decisions that actually matter, and the ones that are just noise. Table of Contents Why "Adding AI" Isn't One Thing Step 1: Find the Right Entry Point Step 2: Choose Your Integration Pattern Step 3: Design the Architecture Step 4: Handle Cost, Latency, and Reliability Step 5: Ship Small, Measure, Expand Common Mistakes We See Final Thoughts Why "Adding AI" Isn't One Thing "AI integration" gets used as a catch-all term, but it covers very different engineering problems: Generating or rewriting content Answering questions using your product's own data Classifying, tagging, or routing records automatically Predicting outcomes from historical data Automating multi-step workflows end to end Each of these needs a different technical approach. The biggest mistake teams make is picking a technology (usually "let's use an LLM for everything") before defining which of these problems they're actually solving. Step 1: Find the Right Entry Point Before writing any code, look at your product usage data and support tickets for patterns like: Repetitive manual work — users doing the same categorization, summarization, or data entry over and over Search or discovery friction — users struggling to find information that exists in your product Decision bottlenecks — users waiting on judgment calls that follow a somewhat predictable pattern A good first AI feature is narrow, has a clear success metric, and solves a problem your users already complain abou

2026-08-22 原文 →
AI 资讯

The flaky test was right: a 58%-reproducible race in a scroll-reading pipeline's disk cache

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry . Project Overview The Vesuvius Challenge uses machine learning to read carbonized Herculaneum scrolls, which is 2,000-year-old papyrus that got buried by the eruption of Vesuvius and can never be physically unrolled. Its open-source monorepo, ScrollPrize/villa, contains the vesuvius Python package that researchers use to stream multi-terabyte CT scan volumes and train ink-detection models. I was setting up that package on my Windows 11 machine (the project's CI only tests Ubuntu, and the workflow file literally says "Extend this list once the build scripts for macOS and Windows are confirmed"), working with an AI coding assistant to run the test suite on a platform it had never been tested on. One test failed. Then it passed. Then it failed again. Bug Fix or Performance Improvement The test, test_shared_cache_multiprocess_reads_are_not_torn, spawns four processes that read one scroll volume through a shared on-disk chunk cache. Run it once and you might not see anything wrong. So I ran it twelve times: 7 failures out of 12, all PermissionError: [WinError 5] Access is denied. A 58% flake isn't a flake. It's a bug with a coin flip attached. The cache is on the hot path for real usage. It's the component behind the package's documented volume_cache_dir config and the --cache-dir flag of its inference CLI. Any PyTorch DataLoader with num_workers > 0 puts multiple processes into exactly this concurrent pattern, so on Windows, training runs would randomly die mid-epoch. Once I dug in (a standalone reproducer that propagated full worker tracebacks instead of repr(exc)), the failure turned out to have three separate surfaces, each one hiding behind the previous one: Cache-entry commit. The zarr library commits each cache entry with a write-temp-then-os.replace pattern. On POSIX, rename(2) over a file another process has open is legal. On Windows, MoveFileEx(MOVEFILE_REPLACE_EXISTING) return

2026-08-22 原文 →
AI 资讯

UNDERSTANDING THE GIT WORKFLOW

Git is a version control system. Version control, also known as source control, is the practice of tracking and managing changes to software code. Version control systems are software tools that help software teams manage changes to source code over time. Git is used for: Tracking code changes Tracking who made changes Coding collaboration Setting up a new Repository A Git repository is a folder that Git tracks for changes. The repository stores all your project's history and versions. Add files to the folder. The following describes how to set up a new repository: Git Init Initializes git user@localhost $ git init This creates a hidden folder called .git inside your project. This is where Git stores all the information it needs to track your files and history. To see which files are in your project folder, use the ls command: user@localhost $ ls To Check if Git is tracking your new files: user@localhost $ git status The files here could either be tracked or untracked:- Untracked Files Files you've created or copied into the folder, but haven't told Git to watch. Tracked Files Files that Git is watching for changes. To make a file tracked, you need to add it to the staging area. Git Staging Tells Git exactly which files you want to include in your next commit. user@localhost $ git add . Common Commands git add . Stages all new, modified, and deleted files in the current directory and its subdirectories. git add <file> Stages a specific file. git add -A (or --all) Stages all changes across the entire repository, regardless of your current folder location. git add -u Stages modifications and deletions of already-tracked files, ignoring completely new (untracked) files. git add *.txt Stages all files matching a specific pattern (e.g., all text files). Git Commit A commit is like a save point in your project. It records a snapshot of your files at a certain time, with a message describing what changed. user@localhost $ git commit -m " Describe your changes" Pushing Chan

2026-08-22 原文 →
AI 资讯

How We Handle Client-Side CSV Merging Without Server Processing

When merging CSVs in the browser, handling mismatched columns and quoted cells changes everything. Here's how filetools does it. Last week we shipped CSV merge/split/transpose tools for filetools, and the most interesting challenge wasn't CSV parsing - it was handling real-world data without a server. Here's how we handle the hard cases. The Problem: CSV files in the wild are messy. Columns don't always match. A cell value contains a comma and that comma is quoted. Headers are sometimes case-sensitive, sometimes not. When you build on a server, you can run a fast library and stream the result. In the browser, you have to make your merge operation deterministic from first load. Our approach: Column matching: Users specify which columns to merge on (e.g., "id" or "email"). We do a case-insensitive first pass, then check for exact matches. If no match exists, we warn the user and ask them to pick from the detected headers. This upfront clarity saves merge errors later. Quoted cell handling: We follow RFC 4180 strictly - a quote inside a quoted field is escaped as a double quote. Most CSV parsers get this wrong when they're quick. We use the csv-parse library (MIT) vendored into the site, same way we do with PDF and ZIP libraries. Column order: The merge operation respects column order from the first file, then appends any new columns from subsequent files. This is deterministic and reproducible. Why this matters for a browser tool: Server-based CSV tools hide their assumptions - you upload, they merge, you download. If a merge fails, you get an error message and no insight into why. Client-side, the user can see the detected headers, approve or correct them, and re-try immediately. That transparency matters when you're dealing with data that represents real records or transactions. What shipped this week: We added merge, split (by row count or column value), transpose, and comparison tools. The same deterministic, transparent approach applies to each one. Next question

2026-08-22 原文 →
开发者

Building a Full Enterprise-Ready React + Spring Boot Auth Flow: An End-to-End Guide

Introduction Authentication is one of those things that looks simple in a tutorial and becomes surprisingly complex in production. Between token storage, CSRF protection, refresh flows, and protected routing, there are many places to get it wrong—and getting it wrong has real security consequences. In two earlier posts, I covered pieces of this puzzle: Enabling CSRF in a JWT-Based React + Spring Boot Application and Storing Personal Information in React: sessionStorage vs Context API . This post ties those threads together into a complete, end-to-end authentication flow you can adapt for enterprise applications. We'll walk through the full journey: login → token issuance → secure storage → protected routes → token refresh → logout. Architecture Overview Before the code, here's the high-level flow: ┌──────────────┐ ┌──────────────────┐ │ React │ │ Spring Boot │ │ Frontend │ │ Backend │ └──────┬───────┘ └────────┬─────────┘ │ 1. POST /login │ │─────────────────────────>│ │ │ validate credentials │ 2. JWT (httpOnly cookie)│ issue access + refresh │<─────────────────────────│ │ │ │ 3. GET /protected │ │ (+ CSRF token) │ │─────────────────────────>│ validate JWT + CSRF │ 4. Protected data │ │<─────────────────────────│ │ │ │ 5. POST /refresh │ │─────────────────────────>│ rotate tokens │ │ │ 6. POST /logout │ │─────────────────────────>│ invalidate session Key Design Decisions Decision Choice Rationale Token storage httpOnly cookies Not accessible to JavaScript → mitigates XSS token theft CSRF protection Double-submit / token pattern Required when using cookies Token type Short-lived access + refresh Limits exposure window State management Context API for auth status Centralized, lightweight Why httpOnly cookies over localStorage? As I discussed in the storage blog, localStorage is readable by any script on the page—making it vulnerable to XSS. httpOnly cookies trade that risk for the need to handle CSRF, which we address below. Step 1: Backend — Login and Token Issuance

2026-08-22 原文 →
AI 资讯

Four places ffmpeg.wasm fails silently in a Next.js app (and the fixes)

I shipped four browser-only video tools with ffmpeg.wasm: trim, compress, video-to-GIF and MP3 extraction. Files never leave the browser, nothing to install. Trim · Compress · GIF · MP3 (Korean UI, but the buttons are obvious) Getting there, I hit four walls. Every one of them surfaced as a single "conversion failed" line in the UI and nothing in the console . Writing them down for the next person. Stack: Next.js App Router + webpack, @ffmpeg/ffmpeg 0.12, self-hosted core. 1. webpack hijacks the dynamic import inside the worker @ffmpeg/ffmpeg spawns its worker like this: new Worker ( new URL ( " ./worker.js " , import . meta . url ), { type : " module " }); webpack recognises the pattern and bundles the worker. Fine. But it also rewrites the import(coreURL) inside that worker to go through its own module loader. The core URL arrives at runtime as a blob: URL, which webpack's loader has never heard of, so it dies with Cannot find module 'blob:...' . The error is thrown inside the worker, so the main-thread console stays empty. Fix: keep the worker out of the bundle. Copy node_modules/@ffmpeg/ffmpeg/dist/esm/worker.js to public/ffmpeg/<version>/lib/ and pass it via classWorkerURL in load() . Now the untouched worker runs. 2. classWorkerURL needs the origin Passing a path like /ffmpeg/0.12.x/lib/worker.js is not enough. The library resolves it with new URL(classWorkerURL, import.meta.url) , and inside the bundle import.meta.url is a build-time file:///C:/... path. So it goes looking for file:///C:/ffmpeg/... and fails. const BASE = `/ffmpeg/ ${ FFMPEG_VERSION } ` ; await ffmpeg . load ({ coreURL : ` ${ location . origin }${ BASE } /core/ffmpeg-core.js` , wasmURL : ` ${ location . origin }${ BASE } /core/ffmpeg-core.wasm` , classWorkerURL : ` ${ location . origin }${ BASE } /lib/worker.js` , }); Prefix location.origin and it works. 3. You cannot build a GIF palette with -vf For decent GIF quality you run palettegen first and paletteuse second. Doing it in one pass needs

2026-08-22 原文 →
AI 资讯

My first website said "Don't commit without context." I never committed it at all.

The renewal notice came and I decided to let it go. threadkeeper.io was my first idea and my first website. I bought the domain in August 2025, about six weeks after a community college AI summer camp where I was writing files with names like ccc-ai-pdf-project and describing them in my own README as a beginner Python project. Then I shipped a domain, a blog, a CLI, and a manifesto. Before I let it lapse I went back to look at it one more time. Sentimental. Five minutes, tops. Then I tried to figure out where the source code lived, and realized it did not live anywhere. The site was on Spaceship. I had built it there, in the browser, and never put it in version control. Not once. There was no repo to clone, no local folder, no backup. The only copy of my first website that existed in the world was the one running on a server I had four days left on. The tagline on that site, in cyan, at the top of the page, was "Don't commit without context." I never committed it at all. I did not have the source code to my own website So the first job was not nostalgia. It was extraction. I pulled all eight pages and every asset off the live server before it went dark: the landing page, the blog, three posts, the Dr. Kahlo page, and the Ariadne Clew recap app I built for an AWS hackathon. Nineteen files. sitemap.xml claimed there were four pages, which tells you how much I trusted my own sitemap in 2025. The rest I found by following links. That archive is now public, with a SHA-256 for every original file so anyone can verify nothing drifted in the rescue: earlgreyhot1701d.github.io/threadkeeper-archive It is committed now. A year late. I named a file dom_js.js and did not blink Here is the first thing I found once I could actually read my own code. The Ariadne Clew app had seven JavaScript modules. Two of them were named with snake case and a suffix: api_js.js , dom_js.js , main_js.js . Four were camelCase with no suffix: utils.js , theme.js , exportMarkdown.js , dragDrop.js . Tw

2026-08-22 原文 →
AI 资讯

A rule-based Korean dialect converter with no LLM: eight guards that keep standard Korean intact

I built a Korean dialect converter that runs entirely in the browser and uses no LLM. Type a standard Korean sentence and it rewrites it the way people speak in Gyeongsang, Jeolla, Chungcheong, Gangwon or Jeju. Paste a dialect sentence and it converts back to standard Korean. Nothing leaves the browser. Try it: https://toolnjoy.com/dialect-converter (Korean UI) The hard part was not producing dialect. It was not breaking standard Korean . Every time I widened a rule, a perfectly normal sentence got mangled somewhere. Here is what I ended up with. Most of it applies to any rule-based text rewriter (honorific converters, profanity filters, spelling normalizers). Shape of the thing Four modules: file job core sentence splitting, sentence-type detection, ending replacement endings per-region ending tables (declarative / interrogative / propositive / imperative × politeness) words per-region vocabulary reverse dialect → standard Forward conversion goes "decide the sentence type, then pick the region's ending". Reverse goes the other way: "look at the ending, infer the sentence type". You cannot just invert the forward table, so reverse has its own table. Each reverse rule carries a type , which removes the detection step entirely. In reverse mode the user does not pick a region. I run all five and keep the one with the highest score (4 points per matching ending + syllable count of matched words). People who paste dialect usually do not know which region it is from. That is why they came. Eight guards, each from a real failure 1. Never reverse a form that appears on the standard side of any table. The Gyeongsang table had 함께 → 같이 (together). Reverse flipped it and rewrote "같이 보게" into "함께 보게", except 같이 is perfectly standard. Fix: anything that ever appears in a standard column anywhere is excluded from reverse candidates. 2. Question endings only count when there is a question mark or a wh-word. Gyeongsang -나 / -노 mark questions. But 하나 ("one") also ends in -나 . Without

2026-08-22 原文 →
AI 资讯

Fixing a null-body crash in the Formbricks survey SDK, found by Sentry

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry . A survey widget should not be able to take down the page it is embedded on. This one could: a single API response with a null body threw an uncaught TypeError in the visitor's browser. Sentry's bot found it, filed it as a GitHub issue, and Seer pointed at the exact line. Here is the fix. Project Overview Formbricks is an open-source survey and experience-management platform. Websites and apps embed a small JavaScript SDK that loads a survey, shows it to a user, and posts the answers back to the Formbricks API. The SDK lives in the monorepo as two packages: @formbricks/js-core (the loader and command queue) and @formbricks/surveys (the survey renderer). Both talk to the backend through a shared makeRequest helper. Bug Fix or Performance Improvement I fixed issue #6581 , a production crash that Sentry filed automatically: Bug: API data is not always validated in the surveys package TypeError: Cannot read properties of null (reading 'data') The issue was opened automatically by sentry[bot] , and its body carries the Sentry-captured (minified) stack trace plus a link to the source event, FORMBRICKS-CLOUD-3VE . Sentry did not just record this crash, it reported it. The SDK calls makeRequest to load a workspace's environment state. That code parsed the HTTP response and immediately read .data off the result: const json = ( await response . json ()) as ApiResponse ; // ... const successResponse = json as ApiSuccessResponse < T > ; return ok ( successResponse . data ); Two things go wrong here: response.json() on a body of literal null returns JavaScript null . Reading null.data throws TypeError: Cannot read properties of null (reading 'data') . The error path had the same problem one line up: errorResponse.code on a null body throws reading 'code' . response.json() is not guarded at all. A non-JSON body (an empty response, or an HTML error page from a proxy or CDN) makes it throw an unhan

2026-08-21 原文 →
AI 资讯

The duration your video API accepts is not the duration it renders

A sequence I cut to a music bed was three frames out at the first transition, nine at the second, and by the sixth segment nothing lined up with anything. I had asked every generation for ten seconds. Every generation had returned a file that was not ten seconds. Nothing in the API said so. The request took duration: 10 , returned 200 , and produced an MP4 whose container duration was 8.708 . No warning field, no note in the response body, and — the part that actually cost me the afternoon — no mention of it on the docs page I had read three times. This is a general property of latent video models rather than a bug in one provider, and once you know the shape of it you can handle it in about twenty lines. Here is the shape. Seconds are the wrong unit A video diffusion model does not work on frames. It works on a compressed latent tensor, and the compression is temporal as well as spatial: a causal 3D autoencoder folds a run of input frames into a single latent frame. Because the encoder is causal, the first frame is kept whole and everything after it is compressed in groups. With a temporal stride of s , a clip of F frames becomes latent_frames = ( F - 1 ) / s + 1 which only divides evenly when F ≡ 1 (mod s) . Frame counts that miss that condition get padded or truncated, so implementations pick the nearest legal count and render that instead. Stack a second constraint on top — many of these models generate in fixed blocks of latent frames rather than one at a time — and the set of renderable lengths collapses into a short arithmetic progression: F = head + block · n n ∈ ℕ Every legal duration is one of those F values divided by the frame rate. Nothing between them is reachable. duration: 10 is not a request. It is a hint that gets snapped to a grid you were never shown. What the snapping does to users Three separate problems, and only the first is obvious. The output is not the length you promised. Your UI said 10s, the file is 8.708s, so your UI lied. Not by much,

2026-08-21 原文 →
AI 资讯

Why I Built a No-Signup QR & URL Utility Platform (And How to Use the API)

We've all been there: a client needs a quick QR code for a print campaign, or a short link for social media. You search Google, click the first result, and realize you need to create an account, verify your email, and potentially pay after 14 days. To solve this friction, we built klick.tools . It's a collection of simple web tools that just work - no signup, no expiration dates, and full GDPR compliance. What's inside? QR code generator: 7 content types (URL, text, WiFi, vCard, email, phone, geo), custom colors and module shapes, automatic WCAG contrast check, and clean export as PNG, SVG or PDF. Rendering happens in the browser, so the payload never leaves the device. URL shortener: shorten a link in seconds, see click stats, and change the target later without reprinting anything. The developer API We didn't want to build just another consumer site, so everything is backed by a REST API. Base URL: https://klick.tools/api/v1 . Responses are always JSON - lists as { data, count } , writes as { message, data } , errors as { error } with a stable machine-readable code . Creating a short link is a single POST, and it works without an account at all (rate-limited per IP): curl -X POST https://klick.tools/api/v1/links \ -H "Content-Type: application/json" \ -d '{"targetUrl": "https://example.com/a-very-long-campaign-url"}' With an API key ( kt_live_... , generated in your account) the link is bound to you, so you get click counts, editing and higher quotas. Pass it as a Bearer token or via x-api-key : const res = await fetch ( " https://klick.tools/api/v1/links " , { method : " POST " , headers : { " Content-Type " : " application/json " , Authorization : `Bearer ${ process . env . KLICK_TOOLS_API_KEY } ` , }, body : JSON . stringify ({ targetUrl : " https://example.com/landing " , title : " Summer campaign " , }), }); const { data } = await res . json (); console . log ( data . shortUrl , data . clickCount ); The same pattern covers QR codes via /api/v1/qr - create, li

2026-08-21 原文 →
AI 资讯

Extending the Login Session to 1 Year for Kiosk‑Mode TV Screens (Next.js API Route)

Extending the Login Session to 1 Year for Kiosk‑Mode TV Screens (Next.js API Route) TL;DR: I changed the maxAge of the auth cookie from 30 days to 365 days in src/app/api/login/route.ts . The tweak lets a TV kiosk stay logged in without a daily refresh, while keeping the same security flags. The Problem Our kiosk‑mode deployment runs on large‑format TVs that display a live dashboard. The UI is protected by the same JWT‑based authentication we use for the web app. After a user logs in, the server sets a Set-Cookie header with the token: cookie : serialize ( " token " , jwt , { httpOnly : true , secure : true , sameSite : " lax " , path : " / " , maxAge : 60 * 60 * 24 * 30 , // 30 days }); In practice, the TVs are turned on once a week and are expected to stay signed in for months. After 30 days the cookie expires, the dashboard silently redirects to the login page, and a technician has to manually re‑authenticate the device. The symptom was a 401 Unauthorized error after exactly 30 days, logged as: Error: No valid session cookie found (maxAge expired) The root cause: the maxAge value was hard‑coded to 30 days, which is fine for browsers but not for unattended kiosks. What I Tried First My initial thought was to keep the 30‑day limit and simply refresh the token on every API call . I added a middleware that called the login endpoint silently if a request lacked a valid token. The flow looked like this: // pseudo‑middleware if ( ! req . cookies . token ) { await fetch ( " /api/login " , { method : " POST " , body : storedCredentials }); } What went wrong? Rate limiting – The middleware hit the login endpoint on every request that missed a token, quickly exhausting the auth provider's rate limit. State leakage – Storing credentials on the client (even in a server‑side environment) introduced a security surface. Complexity – The extra round‑trip added latency and made the code harder to debug. After a few failed attempts (and a stack trace full of 429 Too Many Requests )

2026-08-21 原文 →
AI 资讯

Amazon S3 Hands-On Practicals

I recently worked through a hands-on Amazon S3 practical series covering the features I would actually expect to use while working with AWS storage. Instead of only documenting definitions, this post focuses on what I configured, the commands I used, how I verified the behavior, and what I observed when something went wrong . For the concepts behind these practicals, I have already covered S3 in two detailed sessions: Session 1: AWS S3 Deep Dive — Objects, Encryption, Bucket Policies & Everything In Between Session 2: AWS S3 — Versioning, Static Hosting, CORS, Object Lock & More This post is the practical companion to those two sessions. The concepts are covered there; here I focus on actually building, testing, verifying, and troubleshooting the S3 features. The labs covered: S3 bucket configuration and lifecycle management Bucket policies with IAM, EC2 and HTTPS-only access SSE-KMS encryption with CloudTrail verification Pre-signed URLs AWS CLI s3 sync S3 Versioning and version recovery Static website hosting S3 CORS S3 Object Lock Note: This is a practical write-up, so I have intentionally kept the focus on implementation and verification rather than turning it into a generic S3 theory article. 1. S3 Bucket Configuration and Lifecycle Management Objective Create an S3 bucket with a secure baseline and configure a lifecycle rule that automatically transitions objects to lower-cost storage classes over time. Configuration For the lab: Block Public Access remained enabled. Bucket Versioning was enabled. Lifecycle rule: s3-lab-lifecycle The rule applied to all objects. Current objects transition to: Standard-IA after 30 days Glacier Flexible Retrieval after 90 days The lifecycle flow was: Day 0 ↓ Object uploaded ↓ Day 30 → Standard-IA ↓ Day 90 → Glacier Flexible Retrieval Result The lifecycle rule was successfully created and enabled, and the S3 console confirmed the configured transition periods. What this demonstrates Instead of manually moving old objects, S3 Life

2026-08-21 原文 →
AI 资讯

Powerful regression tests for your PostgreSQL project

Mark (aka Winsaucerer) here to show you how you can test your PostgreSQL database like a sorcerer. We are going to be using Spawn, a SQL build system supporting migrations and testing. You do not need to be using Spawn for migrations in order to use it for testing. Spawn does not require any extension installed. All you need is the spawn CLI and a psql connection to the database for Spawn to connect through. Spawn was built to solve some migration pains I've experienced, but I happily discovered that when used for testing, it is very powerful. To show you some of that power, we're going to use a contrived database example. It uses golden file testing to determine success. When the test runs, we capture the stdout and stderr output from psql, and compare that to expected output. Testing with Spawn involves these steps: Create a new test with spawn test new <name> and fill out the test steps Check test outputs with spawn test run <name> (or view the SQL that will be sent to psql via spawn test build <name> ) When outputs are as expected, create the golden file with spawn test expect <name> Run the test and compare to expected output with spawn test compare <name> For now, Spawn only supports connecting via psql, which means that you have access to all the features that psql provides. To get started, follow the Spawn install instructions: Install Spawn And then create a new folder on your system, and initialise a new project with a docker compose config ready for us to play with: # inside your new folder: spawn init --docker docker compose up -d You now have a running docker based PostgreSQL database and a spawn.toml file configured to connect to it. We are not assuming that you are using Spawn or any other tool for migrations, so you can manually create and update the database by connecting directly using psql: docker exec -ti postgres-db psql -U postgres Create the database ⚠️ Caution This post is not intended as an example of how to build an orders database. The des

2026-08-21 原文 →
AI 资讯

Code Smell 321 - Getter Piggybacking

One broken window invites another TL;DR: Don't reuse an existing getter to bolt on new business logic from outside the object. Problems 😔 Duplicated business rules Broken encapsulation Scattered comparison logic Hidden domain knowledge Fragile refactoring Law of Demeter violation Solutions 😃 Add real behavior methods Keep comparisons inside object Pass collaborators, not primitives Reserve getters for rendering Follow tell, don't ask Refactorings ⚙️ Refactoring 027 - Remove Getters Maxi Contieri Maxi Contieri Maxi Contieri Follow Apr 18 '25 Refactoring 027 - Remove Getters # webdev # programming # beginners # java 3 reactions Add Comment 17 min read Refactoring 013 - Remove Repeated Code Maxi Contieri Maxi Contieri Maxi Contieri Follow Jun 16 '24 Refactoring 013 - Remove Repeated Code # webdev # beginners # programming # tutorial 2 reactions Add Comment 3 min read Context 💬 An object exposes a getter for one legitimate reason: some other part of the system needs to read that value, usually to display it. Getters are a code smell, but this one gets a pass, for now. Later on, you discover that you need new business logic that depends on the same value. You already have the getter, so you write a function outside the object that calls it and does the comparison itself, breaking the encapsulation principle. Someone else needs slightly different logic based on the same value. They also call the getter and write their own version of the comparison. Now two places decide what that value means , and neither of them is the object that owns it. Typical. You didn't add a second getter this time. You reused the first one, because it was already there. That's the trap. The getter existed for one reason, and you let it justify skipping the real fix: a method on the object that answers the question itself, instead of handing out the raw value for every caller to interpret on their own. Sample Code 💻 Wrong 🚫 // Food needs to show its use-by date on the shelf // label, so useByDate(

2026-08-21 原文 →