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

标签:#bestpractices

找到 8 篇相关文章

AI 资讯

Git: The Fellowship of the Commit – Best Practices for Solo Devs and Teams

The Quest Begins (The "Why") I still remember the first time I tried to track down a bug that only showed up after midnight. I opened my terminal, typed git log , and was greeted by a wall of commits that read like a toddler’s grocery list: * 7a9c3f1 (HEAD -> main ) fix stuff * 4b2e8a1 update * f1d9c6b wip * 9e3b7d2 more changes * … I spent three hours chasing a regression that turned out to be a one‑line typo in a file I hadn’t touched in weeks. The commit messages gave me zero clues, and the diff was a tangled mess of unrelated changes. I felt like I was wandering through a dungeon without a map, hoping the next room would hold the answer. That night I realized the real monster wasn’t the bug—it was the way I was committing code. My commits were large, vague, and scattered , making every subsequent step (review, revert, bisect) a gamble. If I wanted to keep my sanity (and maybe even enjoy coding again), I needed a better system. The Revelation (The Insight) The turning point came when I read about Conventional Commits —a lightweight convention that gives each commit a clear type ( feat , fix , docs , refactor , test , chore , etc.) and a short, descriptive message. It sounded simple, but the impact was massive: Atomicity – each commit does one thing. Clarity – the message tells you why the change exists, not just what changed. Automation – tools can generate changelogs, version bumps, and even release notes straight from the log. Adopting this felt like discovering a hidden shortcut in a Zelda dungeon—suddenly the whole map made sense, and I could sprint to the boss room with confidence. Wielding the Power (Code & Examples) Before – The Chaos Imagine we’re building a tiny API for user profiles. Here’s what a typical day of committing looked like (messages only, but the diffs were just as messy): $ git log --oneline -5 7a9c3f1 ( HEAD -> main ) fix stuff 4b2e8a1 update profile handler f1d9c6b wip 9e3b7d2 added auth middleware c5d4e3f refactor utils If I needed to ro

2026-07-12 原文 →
AI 资讯

The Go Code Review Comments List: 10 Rules Every Reviewer Cites

Book: The Complete Guide to Go Programming Also by me: Hexagonal Architecture in Go — the companion book in the Thinking in Go series My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a pull request in a Go repo you just joined. Ten minutes later there are six comments on it. None of them are about your logic. They point at a capitalized error string, a receiver named this , a context stored on a struct. Each comment links the same page: the Go Code Review Comments wiki. That page is the closest thing Go has to an official style council. It grew out of the comments Go's own maintainers left on CLs for years, and most Go teams treat it as the default rulebook. The problem is that the wiki tells you what but rarely why , so the rules read like arbitrary taste. They aren't. Each one exists because the alternative bit somebody. Here are ten that reviewers cite the most, with the reason behind each. Everything below is idiomatic on Go 1.23+. 1. Error strings are lowercase and unpunctuated The rule: an error string should not be capitalized and should not end with punctuation. // wrong return fmt . Errorf ( "Failed to open config." ) // right return fmt . Errorf ( "failed to open config" ) The reason is wrapping. Go errors get concatenated. Your string is almost never the whole sentence a user reads; it's a fragment in a chain built with %w : return fmt . Errorf ( "load settings: %w" , err ) // -> "load settings: open config: permission denied" Capitalize your fragment and you get load settings: Open config: permission denied in the middle of a line. End it with a period and you get a period in the middle of a longer message. Lowercase, no trailing punctuation, and every fragment composes cleanly no matter where it lands in the chain. The exception is a string that begins with an exported name or acronym, which keeps its own case ( HTTP , TLS ). 2. Receiver types are consistent ac

2026-07-03 原文 →
AI 资讯

Go Naming: Why Getters Drop the Get Prefix (and Other Idioms)

Book: The Complete Guide to Go Programming Also by me: Hexagonal Architecture in Go — the companion book in the Thinking in Go series My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You write a Go struct with a private field and a method to read it. Muscle memory from Java, C#, or PHP takes over, and you type GetName() . The code compiles. Tests pass. Then a reviewer leaves a comment that just says "drop the Get" with no explanation, and you're left wondering whether that's a real rule or one person's taste. It's a real rule. It's written into the language's own style guide, the standard library follows it everywhere, and several linters will flag the code that breaks it. Go naming isn't a matter of opinion the way it is in some languages. A handful of conventions are baked into the tooling, and once you know them, half the reviewer nitpicks disappear. Getters drop the Get The convention comes straight from the Effective Go document. If you have an unexported field owner , the accessor is named Owner , not GetName or GetOwner . The mutator, if you need one, keeps the Set prefix. type File struct { owner string } func ( f * File ) Owner () string { return f . owner } func ( f * File ) SetOwner ( o string ) { f . owner = o } The reasoning is about how the call reads. person.Name() says the same thing as GetName() with less noise, and the parentheses already tell you it's a method call. Get adds a word that carries no information in a language where field access and method calls look different anyway. The Set prefix stays because there's no other clean way to signal mutation. Name() reads, SetName(x) writes. The asymmetry is deliberate. This shows up all over the standard library. bytes.Buffer has Len() , not GetLen() . sync.Once has no getters to get wrong, but http.Request exposes fields and methods that never carry a Get . time.Time has Hour() , Minute() , Second() . The pattern is

2026-07-03 原文 →
AI 资讯

The Dependency Injection Quest: How I Turned Spaghetti Code Into a Lightsaber 🚀

The Quest Begins (The “Why”) Picture this: I’m knee‑deep in a legacy codebase that feels like the Death Star’s trash compactor—every time I try to add a feature, the walls close in and I’m squashed by tight coupling. I’d just spent three hours tracking down a bug that only showed up when the payment gateway was mocked in a test. The culprit? A new PaymentGateway() buried deep inside an OrderService class. It was like trying to defeat Darth Vader with a butter knife—no matter how hard I swung, the Dark Force (aka hidden dependencies) kept pulling me back. I realized I was instantiating collaborators inside the very classes that should be oblivious to their implementation details . The result? Tests that needed a real database, a real Stripe account, and a sacrificial goat to run. Any change to a third‑party API meant hunting down every new scattered across the project. Onboarding a new teammate felt like handing them a map written in ancient Sumerian. Honestly, I was ready to quit coding and become a professional napper. Then, during a late‑night coffee‑fueled refactor session, I stumbled upon a tiny line of documentation that whispered: “Depend on abstractions, not concretions.” It sounded like Yoda giving me a pep talk. The Revelation (The Insight) The magic spell I uncovered is Dependency Injection (DI) —specifically, constructor injection . Instead of a class creating its own collaborators, we hand them in from the outside. Think of it as giving a Jedi their lightsaber rather than making them forge one in the middle of a battle. Why does this feel like discovering the Force? Testability explodes – you can swap in fakes, mocks, or stubs without touching production code. Flexibility skyrockets – swapping a payment provider becomes a one‑line config change, not a scavenger hunt. Clarity reigns – the constructor becomes an honest inventory of what a class needs to do its job. The moment I applied it, the codebase felt lighter, like Luke finally trusting the Force ins

2026-06-18 原文 →
AI 资讯

Struct Embedding in Go: Composition That Bites When You Reach for Inheritance

Book: The Complete Guide to Go Programming Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You come to Go from a language with classes. You see struct embedding for the first time, and it reads like inheritance. A field with no name, methods that "carry over" to the outer type, a base struct that your type extends. So you write code the way you always have, and most of it works. Then a method does something you did not ask for, a type satisfies an interface you never meant to implement, or two embedded types fight over a name and the compiler shrugs until the exact line that calls it. Embedding is not inheritance. It is composition with a syntax that promotes methods and fields up one level. Once you hold that distinction, the surprises stop being surprises. Here is where they come from. Embedding promotes, it does not subclass Write an embedded field by giving a type with no field name: type Engine struct { Horsepower int } func ( e Engine ) Start () string { return "vroom" } type Car struct { Engine // embedded Brand string } Car now has a Start method and a Horsepower field, both promoted from Engine . You can write car.Start() and car.Horsepower as if they were declared on Car . car := Car { Engine : Engine { Horsepower : 300 }, Brand : "Fiat" } fmt . Println ( car . Start ()) // vroom fmt . Println ( car . Horsepower ) // 300 This is where the inheritance illusion starts. car.Start() is sugar. The compiler rewrites it to car.Engine.Start() . The receiver of Start is still an Engine , never a Car . There is no base class, no super , no virtual dispatch. Engine does not know Car exists. That last point is the one that bites. A promoted method runs against the embedded value, not the outer struct. The method that ignores the outer struct Say you want a stringer on the embe

2026-06-14 原文 →
AI 资讯

defer in Loops: The Resource Leak Go Still Lets You Write

Book: The Complete Guide to Go Programming Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You wrote a function that walks a directory and reads every file. It opened cleanly in review. It passed tests on a fixture folder with three files. Then a customer pointed it at a folder with 20,000 files, and the logs filled with: open /data/batch/img-08431.png: too many open files The code never closed anything early. It deferred every close. And that is exactly the problem. defer runs at function return, not end of iteration defer schedules a call to run when the surrounding function returns. Not when the current block ends. Not when the loop iteration ends. When the function returns. Most of the time that distinction does not matter, because most deferred calls live in short functions that return quickly. Put a defer inside a for loop, though, and every iteration adds one more deferred call to a stack that does not unwind until the whole loop is done and the function exits. Here is the version that ships: func processFiles ( paths [] string ) error { for _ , p := range paths { f , err := os . Open ( p ) if err != nil { return err } defer f . Close () // runs at RETURN, not here if err := handle ( f ); err != nil { return err } } return nil } Read it the way the language reads it. On the first iteration, os.Open returns a file handle and you defer its close. On the second iteration, another handle, another deferred close. By the time the loop has touched 20,000 files, you are holding 20,000 open descriptors, and not one of them closes until processFiles returns. Your process has a file-descriptor limit. On Linux the soft default is often 1024. You blow through it somewhere around the 1024th file, and the error you get back says nothing about defer . It says too many open files , wh

2026-06-14 原文 →
AI 资讯

How to Format SQL Queries in Python: Best Practices, Gotchas, and Real-World Examples

Stop writing SQL strings that look like a ransom note. Here's how to write queries that are readable, safe, and maintainable. The Problem With "Good Enough" SQL Formatting Most Python developers start here: user_id = 5 query = " SELECT * FROM users WHERE id = " + str ( user_id ) cursor . execute ( query ) It works. Until it doesn't — and when it breaks, it breaks badly : SQL injection, cryptic errors from mismatched types, and queries that take 45 minutes to debug at 2am. Let's fix that, permanently. 1. Never Concatenate User Input — Use Parameterized Queries This is rule #1 and it's non-negotiable. ❌ The Wrong Way (SQL Injection Waiting to Happen) username = request . args . get ( " username " ) # Could be: ' OR '1'='1 query = f " SELECT * FROM users WHERE username = ' { username } '" cursor . execute ( query ) If username is ' OR '1'='1 , your entire users table just got exposed. ✅ The Right Way: Parameterized Queries username = request . args . get ( " username " ) # psycopg2 (PostgreSQL) cursor . execute ( " SELECT * FROM users WHERE username = %s " , ( username ,)) # sqlite3 cursor . execute ( " SELECT * FROM users WHERE username = ? " , ( username ,)) # SQLAlchemy Core from sqlalchemy import text result = conn . execute ( text ( " SELECT * FROM users WHERE username = :name " ), { " name " : username }) The database driver handles escaping. You never touch it. This pattern is immune to SQL injection by design. Gotcha: Note the trailing comma in (username,) . Without it, Python treats the string as an iterable and passes each character as a separate parameter. This is one of the most common beginner bugs. # 💥 Bug: passes ('a', 'l', 'i', 'c', 'e') instead of ('alice',) cursor . execute ( " SELECT * FROM users WHERE username = %s " , ( username )) # ✅ Correct: single-element tuple cursor . execute ( " SELECT * FROM users WHERE username = %s " , ( username ,)) 2. Multi-Line Queries: Triple Quotes + Consistent Indentation For anything longer than one clause, use tri

2026-06-11 原文 →
AI 资讯

The One TDD Habit That Saved My Sanity (and My Codebase)

The One TDD Habit That Saved My Sanity (and My Codebase) Quick context (why you're writing this) Here's the thing: I used to think I was doing TDD right. I’d write a test, watch it fail, then write just enough code to make it green. Rinse and repeat. Sounds textbook, right? But a few months ago I spent an entire afternoon chasing a bug that only showed up after I refactored a service class. The tests were all passing, yet the app was throwing NullReferenceExceptions in production. I was shocked. How could everything be green and still be broken? Turns out I was testing the inside of my code instead of what it actually did for the outside world. That realization hit me like a truck, and it completely changed how I approach TDD. The Insight Test behavior, not implementation. If your test is coupled to private fields, internal data structures, or the exact way a method accomplishes its goal, you’re not testing what matters—you’re testing how you happen to do it today. When you later refactor to improve performance, swap out a dependency, or even just rename a variable, those tests start failing for no good reason. You end up spending more time fixing tests than delivering value, and you lose confidence in the suite because it feels fragile. The payoff? A test suite that gives you confidence when you change code, not anxiety. You can refactor fearlessly because the tests only care about the contract: given these inputs, the system should produce these outputs or side‑effects . How (with code) Let’s look at a tiny but realistic example: a PasswordValidator service that checks whether a user‑chosen password meets our policy. ❌ The mistake: testing implementation details // PasswordValidator.cs public class PasswordValidator { private readonly IRegexProvider _regex ; // injected for testability public PasswordValidator ( IRegexProvider regex ) { _regex = regex ; } public bool IsValid ( string password ) { // implementation we might want to change later return _regex . IsMa

2026-06-05 原文 →