[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"sanity-7uuWLX01ZrzYPQtxB6WCjFDUqtU6hCFqW9W9zkJLkV0":3,"sanity-yTIKQ4jNZz3k9eUNtUhQD1t88OaxDfiE9nGRRCUPV4g":1303},{"data":4,"sourceMap":-1},{"latestPodcast":5,"latestReleases":14,"post":39,"recent":1278},[6],{"_id":7,"publishedAt":8,"slug":9,"sponsored":12,"title":13},"f83eb5f0-1237-487f-84d8-f7abf2318c39","2026-06-25T07:40:00.000Z",{"_type":10,"current":11},"slug","code-isnt-causing-your-production-failures",null,"Code isn’t the only thing causing your production failures",[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":1243,"dateUrl":1244,"excerpt":1245,"image":1246,"legacyBody":1249,"product":12,"publishedAt":1252,"slug":1253,"sponsored":12,"tags":1255,"title":1277,"visible":1243},"2023-05-24T12:51:04Z","wp-post-19908","OuLxq6F1ZwNJHk6WTUPUo0",{"base":44},{"id":41,"rev":45},"Soh4zwnVSgD0IUkr57LP6F","blogPost","2026-05-11T18:32:58Z",[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-19328","dgl3SCUzppW3U2LvCoP6c0","blogAuthor","2023-06-20T15:05:12Z",{"_type":56,"asset":57},"image",{"_ref":58,"_type":59},"image-86a0c56b829a0bbe0f28e601dd213fe0e769b7b6-40x40-jpg","reference","","none","Mark Seeman",{"current":64},"mark-seeman",[66,78,142,147,171,180,188,204,222,230,238,246,254,262,270,274,312,320,328,331,339,355,364,372,376,391,399,407,415,434,442,446,454,462,470,478,509,517,525,575,583,591,599,607,622,624,632,648,651,659,667,670,694,702,705,728,736,770,814,817,833,841,844,852,860,904,919,921,929,932,940,943,950,953,961,969,984,1004,1012,1015,1030,1038,1041,1057,1065,1073,1089,1097,1105,1130,1157,1165,1181,1189,1211,1219,1227,1235],{"_key":67,"_type":68,"children":69,"markDefs":76,"style":77},"824508b46cf2","block",[70],{"_key":71,"_type":72,"marks":73,"text":75},"824508b46cf20","span",[74],"em","[Ed. note: While we take some time to rest up over the holidays and prepare for next year, we are re-publishing our top ten posts for the year. Please enjoy our favorite work this year and we’ll see you in 2023.]",[],"normal",{"_key":79,"_type":68,"children":80,"markDefs":130,"style":77},"f4f36dd5741d",[81,85,90,94,99,103,108,112,117,121,126],{"_key":82,"_type":72,"marks":83,"text":84},"f4f36dd5741d0",[],"In the movie ",{"_key":86,"_type":72,"marks":87,"text":89},"f4f36dd5741d1",[88],"70b8b38336f0","Free Solo",{"_key":91,"_type":72,"marks":92,"text":93},"f4f36dd5741d2",[]," the rock climber ",{"_key":95,"_type":72,"marks":96,"text":98},"f4f36dd5741d3",[97],"21ef7b247464","Alex Honnold",{"_key":100,"_type":72,"marks":101,"text":102},"f4f36dd5741d4",[]," trains to perform a ",{"_key":104,"_type":72,"marks":105,"text":107},"f4f36dd5741d5",[106],"8b3ecc43a793","free solo climb",{"_key":109,"_type":72,"marks":110,"text":111},"f4f36dd5741d6",[]," of ",{"_key":113,"_type":72,"marks":114,"text":116},"f4f36dd5741d7",[115],"2f628dc9dd18","El Capitan",{"_key":118,"_type":72,"marks":119,"text":120},"f4f36dd5741d8",[],", a mountain in ",{"_key":122,"_type":72,"marks":123,"text":125},"f4f36dd5741d9",[124],"b501ccf522c9","Yosemite",{"_key":127,"_type":72,"marks":128,"text":129},"f4f36dd5741d10",[],".",[131,134,136,138,140],{"_key":88,"_type":132,"href":133,"reference":12},"link","https://en.wikipedia.org/wiki/Free_Solo",{"_key":97,"_type":132,"href":135,"reference":12},"https://en.wikipedia.org/wiki/Alex_Honnold",{"_key":106,"_type":132,"href":137,"reference":12},"https://en.wikipedia.org/wiki/Free_solo_climbing",{"_key":115,"_type":132,"href":139,"reference":12},"https://en.wikipedia.org/wiki/El_Capitan",{"_key":124,"_type":132,"href":141,"reference":12},"https://en.wikipedia.org/wiki/Yosemite_National_Park",{"_key":143,"_type":56,"alt":12,"asset":144,"caption":146,"markDefs":12},"03a965671b39",{"_ref":145,"_type":59},"image-e69026730cb79a69f47067a923888e29060e796c-640x427-png","(El Capitan. Photo by Mike Murphy, 2005.)",{"_key":148,"_type":68,"children":149,"markDefs":170,"style":77},"eeaa74091030",[150,154,158,162,166],{"_key":151,"_type":72,"marks":152,"text":153},"eeaa740910300",[],"It's a good movie, but if you haven't seen it, ",{"_key":155,"_type":72,"marks":156,"text":157},"eeaa740910301",[74],"free solo climbing",{"_key":159,"_type":72,"marks":160,"text":161},"eeaa740910302",[]," is when you scale a rock face without ropes, harness, or safety equipment. If you lose your grip and fall, you'll die. El Capitan, just to rub it in, is 914 meters of vertical rock. Free-climbing it is an incredible endeavor, but Honnold gets it done by ",{"_key":163,"_type":72,"marks":164,"text":165},"eeaa740910303",[74],"committing ",{"_key":167,"_type":72,"marks":168,"text":169},"eeaa740910304",[],"to one move at a time (this article is about using Git, after all).",[],{"_key":172,"_type":68,"children":173,"markDefs":178,"style":179},"d8a715123cc1",[174],{"_key":175,"_type":72,"marks":176,"text":177},"d8a715123cc10",[],"Save point",[],"h1",{"_key":181,"_type":68,"children":182,"markDefs":187,"style":77},"0b4b9c9c4b4c",[183],{"_key":184,"_type":72,"marks":185,"text":186},"0b4b9c9c4b4c0",[],"Honnold didn't just free-climb El Capitan. He trained deliberately towards the goal of free climbing El Capitan.",[],{"_key":189,"_type":68,"children":190,"markDefs":203,"style":77},"56d94df8c2da",[191,195,199],{"_key":192,"_type":72,"marks":193,"text":194},"56d94df8c2da0",[],"The documentary shows how he repeatedly climbs El Capitan ",{"_key":196,"_type":72,"marks":197,"text":198},"56d94df8c2da1",[74],"with",{"_key":200,"_type":72,"marks":201,"text":202},"56d94df8c2da2",[]," safety equipment. He plans a route and climbs it several times. On each of the training ascensions, he's using ropes, a harness, and various fasteners for the ropes. When he falls during training, he doesn't fall far, because the rope, harness, and fasteners stop the fall at the last point of fixation.",[],{"_key":205,"_type":68,"children":206,"markDefs":219,"style":77},"bd2e0077dcee",[207,211,216],{"_key":208,"_type":72,"marks":209,"text":210},"bd2e0077dcee0",[],"It's almost like a ",{"_key":212,"_type":72,"marks":213,"text":215},"bd2e0077dcee1",[214],"8eaa1538e813","video game save point",{"_key":217,"_type":72,"marks":218,"text":129},"bd2e0077dcee2",[],[220],{"_key":214,"_type":132,"href":221,"reference":12},"https://en.wikipedia.org/wiki/Saved_game",{"_key":223,"_type":68,"children":224,"markDefs":229,"style":77},"6ddb082d5843",[225],{"_key":226,"_type":72,"marks":227,"text":228},"6ddb082d58430",[],"In one memorable scene, Honnold considers a jump from one position to another. Hundreds of meters in the air, parallel to a vertical rock face. It's a truly precarious maneuver. If he fails, he'll die.",[],{"_key":231,"_type":68,"children":232,"markDefs":237,"style":77},"a1ef0ef91602",[233],{"_key":234,"_type":72,"marks":235,"text":236},"a1ef0ef916020",[],"Or, that's true for the free climb. At first, he rehearses the move using rope and harness. This enables him to perform a potentially fatal jump in relative safety. When it goes wrong, he's back at where he fixed his rope, and he may try again.",[],{"_key":239,"_type":68,"children":240,"markDefs":245,"style":77},"07d446299536",[241],{"_key":242,"_type":72,"marks":243,"text":244},"07d4462995360",[],"When you’re making large code changes, even migrating to a new implementation, you can create save points to prevent catastrophes. Like Alex Honold, you can fix your code in place to give you a better chance to get to the next successful build.",[],{"_key":247,"_type":68,"children":248,"markDefs":253,"style":179},"ea13cbab0a38",[249],{"_key":250,"_type":72,"marks":251,"text":252},"ea13cbab0a380",[],"Precarious editing",[],{"_key":255,"_type":68,"children":256,"markDefs":261,"style":77},"f155c8e1c423",[257],{"_key":258,"_type":72,"marks":259,"text":260},"f155c8e1c4230",[],"When you edit code, you go from one working state to another, but during the process, the code doesn't always run or compile.",[],{"_key":263,"_type":68,"children":264,"markDefs":269,"style":77},"f131acdb24b4",[265],{"_key":266,"_type":72,"marks":267,"text":268},"f131acdb24b40",[],"Consider an interface like this:",[],{"_key":271,"_type":272,"code":273,"markDefs":12},"b29d047ea484","code","public interface IReservationsRepository\n{\n    Task Create(Reservation reservation);\n \n    Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(\n        DateTime dateTime);\n \n    Task\u003CReservation?> ReadReservation(Guid id);\n \n    Task Update(Reservation reservation);\n \n    Task Delete(Guid id);\n}\n",{"_key":275,"_type":68,"children":276,"markDefs":307,"style":77},"8d0124a58acf",[277,281,286,290,295,299,303],{"_key":278,"_type":72,"marks":279,"text":280},"8d0124a58acf0",[],"This, as most of the code in this article, is from my book ",{"_key":282,"_type":72,"marks":283,"text":285},"8d0124a58acf1",[284],"9b5694daca8e","Code That Fits in Your Head",{"_key":287,"_type":72,"marks":288,"text":289},"8d0124a58acf2",[],". As I describe in the section on the ",{"_key":291,"_type":72,"marks":292,"text":294},"8d0124a58acf3",[293],"6188f45d61d2","Strangler Fig pattern",{"_key":296,"_type":72,"marks":297,"text":298},"8d0124a58acf4",[],", at one point I had to add a new method to the interface. The new method should be an overload of the ",{"_key":300,"_type":72,"marks":301,"text":302},"8d0124a58acf5",[272],"ReadReservations",{"_key":304,"_type":72,"marks":305,"text":306},"8d0124a58acf6",[]," method with this signature:",[308,310],{"_key":284,"_type":132,"href":309,"reference":12},"https://blog.ploeh.dk/code-that-fits-in-your-head",{"_key":293,"_type":132,"href":311,"reference":12},"https://martinfowler.com/bliki/StranglerFigApplication.html",{"_key":313,"_type":68,"children":314,"markDefs":319,"style":77},"0cca540a05c5",[315],{"_key":316,"_type":72,"marks":317,"text":318},"0cca540a05c50",[272],"Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(DateTime min, DateTime max);",[],{"_key":321,"_type":68,"children":322,"markDefs":327,"style":77},"bbdfe94292a7",[323],{"_key":324,"_type":72,"marks":325,"text":326},"bbdfe94292a70",[],"Once you start typing that method definition, however, your code no longer works:",[],{"_key":329,"_type":272,"code":330,"markDefs":12},"16af9e32aa9f","Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(\n    DateTime dateTime);\n \nT\n \nTask\u003CReservation?> ReadReservation(Guid id);",{"_key":332,"_type":68,"children":333,"markDefs":338,"style":77},"eacdc3e9dfd0",[334],{"_key":335,"_type":72,"marks":336,"text":337},"eacdc3e9dfd00",[],"If you're editing in Visual Studio, it'll immediately light up with red squiggly underlines, indicating that the code doesn't parse.",[],{"_key":340,"_type":68,"children":341,"markDefs":354,"style":77},"63e54e5d38d1",[342,346,350],{"_key":343,"_type":72,"marks":344,"text":345},"63e54e5d38d10",[],"You have to type the entire method declaration before the red squiggly lines disappear, but even then, the code doesn't compile. While the interface definition may be syntactically valid, adding the new method broke some other code. The code base contains classes that implement the ",{"_key":347,"_type":72,"marks":348,"text":349},"63e54e5d38d11",[272],"IReservationsRepository",{"_key":351,"_type":72,"marks":352,"text":353},"63e54e5d38d12",[]," interface, but none of them define the method you just added. The compiler knows this, and complains:",[],{"_key":356,"_type":68,"children":357,"markDefs":362,"style":363},"270fc48c23f7",[358],{"_key":359,"_type":72,"marks":360,"text":361},"270fc48c23f70",[],"Error CS0535 'SqlReservationsRepository' does not implement interface member 'IReservationsRepository.ReadReservations(DateTime, DateTime)'",[],"blockquote",{"_key":365,"_type":68,"children":366,"markDefs":371,"style":77},"7b7efa91e1c9",[367],{"_key":368,"_type":72,"marks":369,"text":370},"7b7efa91e1c90",[],"There's nothing wrong with that. I'm just trying to highlight how editing code involves a transition between two working states:",[],{"_key":373,"_type":56,"alt":12,"asset":374,"caption":60,"markDefs":12},"4d9e4447c773",{"_ref":375,"_type":59},"image-a4b8d1b63eed4148436332b875d705ff06193b49-500x126-png",{"_key":377,"_type":68,"children":378,"markDefs":390,"style":77},"f1101414dbf8",[379,383,386],{"_key":380,"_type":72,"marks":381,"text":382},"f1101414dbf80",[],"In ",{"_key":384,"_type":72,"marks":385,"text":89},"f1101414dbf81",[74],{"_key":387,"_type":72,"marks":388,"text":389},"f1101414dbf82",[]," the entire climb is dangerous, but there's a particularly perilous maneuver that Alex Honnold has to make because he can't find a safer route. For most of the climb, he climbs using safer techniques, moving from position to position in small increments, never losing grip or footing as he shifts his center of gravity.",[],{"_key":392,"_type":68,"children":393,"markDefs":398,"style":77},"e0971eae79ee",[394],{"_key":395,"_type":72,"marks":396,"text":397},"e0971eae79ee0",[],"There's a reason he favors climbing like that. It's safer.",[],{"_key":400,"_type":68,"children":401,"markDefs":406,"style":179},"941b6d3674fd",[402],{"_key":403,"_type":72,"marks":404,"text":405},"941b6d3674fd0",[],"Micro-commits",[],{"_key":408,"_type":68,"children":409,"markDefs":414,"style":77},"5d748163384b",[410],{"_key":411,"_type":72,"marks":412,"text":413},"5d748163384b0",[],"You can't edit code without temporarily breaking it. What you can do, however, is move in small, deliberate steps. Every time you reach a point where the code compiles and all tests pass: commit the changes to Git.",[],{"_key":416,"_type":68,"children":417,"markDefs":431,"style":77},"f034a5bcec9b",[418,422,427],{"_key":419,"_type":72,"marks":420,"text":421},"f034a5bcec9b0",[],"Tim Ottinger calls this a ",{"_key":423,"_type":72,"marks":424,"text":426},"f034a5bcec9b1",[425],"5ba189ffb2db","micro-commit",{"_key":428,"_type":72,"marks":429,"text":430},"f034a5bcec9b2",[],". Not only should you commit every time you have a green bar—you should deliberately move in such a way that the distance between two commits is as short as possible. If you can think of alternative ways to change the code, choose the pathway that promises the smallest steps.",[432],{"_key":425,"_type":132,"href":433,"reference":12},"https://www.industriallogic.com/blog/whats-this-about-micro-commits/",{"_key":435,"_type":68,"children":436,"markDefs":441,"style":77},"3157a9ce7363",[437],{"_key":438,"_type":72,"marks":439,"text":440},"3157a9ce73630",[],"Why make dangerous leaps when you can advance in small, controlled moves?",[],{"_key":443,"_type":56,"alt":12,"asset":444,"caption":60,"markDefs":12},"d70540b2db1f",{"_ref":445,"_type":59},"image-5dcdf4b74ac463c87061046783154d77e7b68dd6-500x121-png",{"_key":447,"_type":68,"children":448,"markDefs":453,"style":77},"09518acd78e5",[449],{"_key":450,"_type":72,"marks":451,"text":452},"09518acd78e50",[],"Git is a wonderful tool for maneuverability. Most people don't think of it like that. They start programming, and hours later, they may commit to Git in order to push a branch out.",[],{"_key":455,"_type":68,"children":456,"markDefs":461,"style":77},"739ab5a45f70",[457],{"_key":458,"_type":72,"marks":459,"text":460},"739ab5a45f700",[],"Tim Ottinger doesn't do that, and neither do I. I use Git tactically.",[],{"_key":463,"_type":68,"children":464,"markDefs":469,"style":77},"3ac00fcf8a4f",[465],{"_key":466,"_type":72,"marks":467,"text":468},"3ac00fcf8a4f0",[],"I'll walk you through an example.",[],{"_key":471,"_type":68,"children":472,"markDefs":477,"style":179},"ab7fe22e9660",[473],{"_key":474,"_type":72,"marks":475,"text":476},"ab7fe22e96600",[],"Adding an interface method",[],{"_key":479,"_type":68,"children":480,"markDefs":507,"style":77},"53583b8eaa0a",[481,485,488,492,495,499,503],{"_key":482,"_type":72,"marks":483,"text":484},"53583b8eaa0a0",[],"As described above, I wanted to add a ",{"_key":486,"_type":72,"marks":487,"text":302},"53583b8eaa0a1",[272],{"_key":489,"_type":72,"marks":490,"text":491},"53583b8eaa0a2",[]," overload to the ",{"_key":493,"_type":72,"marks":494,"text":349},"53583b8eaa0a3",[272],{"_key":496,"_type":72,"marks":497,"text":498},"53583b8eaa0a4",[]," interface. The motivation for that is described in ",{"_key":500,"_type":72,"marks":501,"text":285},"53583b8eaa0a5",[502],"51fc23efcdaf",{"_key":504,"_type":72,"marks":505,"text":506},"53583b8eaa0a6",[],", but that's not the point here. The point is to use Git to move in small increments.",[508],{"_key":502,"_type":132,"href":309,"reference":12},{"_key":510,"_type":68,"children":511,"markDefs":516,"style":77},"fc23e196b4bf",[512],{"_key":513,"_type":72,"marks":514,"text":515},"fc23e196b4bf0",[],"When you add a new method to an existing interface, the code base fails to compile when you have existing classes that implement that interface. How do you deal with that situation? Do you just forge ahead and implement the new method? Or are there alternatives?",[],{"_key":518,"_type":68,"children":519,"markDefs":524,"style":77},"0fd4440bf744",[520],{"_key":521,"_type":72,"marks":522,"text":523},"0fd4440bf7440",[],"Here's an alternative path that moves in smaller increments.",[],{"_key":526,"_type":68,"children":527,"markDefs":572,"style":77},"25141bf97d6f",[528,532,536,540,545,549,553,557,561,565,568],{"_key":529,"_type":72,"marks":530,"text":531},"25141bf97d6f0",[],"First, ",{"_key":533,"_type":72,"marks":534,"text":535},"25141bf97d6f1",[74],"lean on the compiler",{"_key":537,"_type":72,"marks":538,"text":539},"25141bf97d6f2",[]," (as ",{"_key":541,"_type":72,"marks":542,"text":544},"25141bf97d6f3",[543],"8c39ba48f28d","Working Effectively with Legacy Code",{"_key":546,"_type":72,"marks":547,"text":548},"25141bf97d6f4",[]," puts it). The compiler errors tell you which classes lack the new method. In the example code base, it's ",{"_key":550,"_type":72,"marks":551,"text":552},"25141bf97d6f5",[272],"SqlReservationsRepository",{"_key":554,"_type":72,"marks":555,"text":556},"25141bf97d6f6",[]," and ",{"_key":558,"_type":72,"marks":559,"text":560},"25141bf97d6f7",[272],"FakeDatabase",{"_key":562,"_type":72,"marks":563,"text":564},"25141bf97d6f8",[],". Open one of those code files, but don't do anything yet. Instead, copy the new ",{"_key":566,"_type":72,"marks":567,"text":302},"25141bf97d6f9",[272],{"_key":569,"_type":72,"marks":570,"text":571},"25141bf97d6f10",[]," method declaration to the clipboard. Then stash the changes:",[573],{"_key":543,"_type":132,"href":574,"reference":12},"https://blog.ploeh.dk/ref/wewlc",{"_key":576,"_type":68,"children":577,"markDefs":582,"style":77},"42c410a36bd2",[578],{"_key":579,"_type":72,"marks":580,"text":581},"42c410a36bd20",[272],"$ git stash",[],{"_key":584,"_type":68,"children":585,"markDefs":590,"style":77},"ce1f567d5f8b",[586],{"_key":587,"_type":72,"marks":588,"text":589},"ce1f567d5f8b0",[272],"Saved working directory and index state WIP on tactical-git: [...]",[],{"_key":592,"_type":68,"children":593,"markDefs":598,"style":77},"b0613b9ee05b",[594],{"_key":595,"_type":72,"marks":596,"text":597},"b0613b9ee05b0",[],"The code is now back in a working state. Now find a good place to add the new method to one of the classes that implement the interface.",[],{"_key":600,"_type":68,"children":601,"markDefs":606,"style":179},"dc8125a3bc37",[602],{"_key":603,"_type":72,"marks":604,"text":605},"dc8125a3bc370",[],"SQL implementation",[],{"_key":608,"_type":68,"children":609,"markDefs":621,"style":77},"c640f1ef36e0",[610,614,617],{"_key":611,"_type":72,"marks":612,"text":613},"c640f1ef36e00",[],"I’ll start with the ",{"_key":615,"_type":72,"marks":616,"text":552},"c640f1ef36e01",[272],{"_key":618,"_type":72,"marks":619,"text":620},"c640f1ef36e02",[]," class. Once I've navigated to the line in the file where I want to add the new method, I paste in the method declaration:",[],{"_key":623,"_type":272,"code":318,"markDefs":12},"305e69b515f7",{"_key":625,"_type":68,"children":626,"markDefs":631,"style":77},"959d85942d4e",[627],{"_key":628,"_type":72,"marks":629,"text":630},"959d85942d4e0",[],"That doesn't compile because the method ends with a semicolon and has no body.",[],{"_key":633,"_type":68,"children":634,"markDefs":647,"style":77},"47f6a847cc6d",[635,639,643],{"_key":636,"_type":72,"marks":637,"text":638},"47f6a847cc6d0",[],"So I make the method ",{"_key":640,"_type":72,"marks":641,"text":642},"47f6a847cc6d1",[272],"public",{"_key":644,"_type":72,"marks":645,"text":646},"47f6a847cc6d2",[],", delete the semicolon, and add curly brackets:",[],{"_key":649,"_type":272,"code":650,"markDefs":12},"ada5c30d727e","public Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(DateTime min, DateTime max)\n{\n\n}\n",{"_key":652,"_type":68,"children":653,"markDefs":658,"style":77},"34cb1d68347c",[654],{"_key":655,"_type":72,"marks":656,"text":657},"34cb1d68347c0",[],"This still doesn't compile, because the method declaration promises to return a value, but the body is empty.",[],{"_key":660,"_type":68,"children":661,"markDefs":666,"style":77},"ec230283e0ac",[662],{"_key":663,"_type":72,"marks":664,"text":665},"ec230283e0ac0",[],"What's the shortest way to a working system?",[],{"_key":668,"_type":272,"code":669,"markDefs":12},"0f3c147a4597","public Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(DateTime min, DateTime max)\n{\n    throw new NotImplementedException();\n}\n",{"_key":671,"_type":68,"children":672,"markDefs":693,"style":77},"5c0ed1c68c6b",[673,677,681,685,689],{"_key":674,"_type":72,"marks":675,"text":676},"5c0ed1c68c6b0",[],"You may not want to commit code that throws ",{"_key":678,"_type":72,"marks":679,"text":680},"5c0ed1c68c6b1",[272],"NotImplementedException",{"_key":682,"_type":72,"marks":683,"text":684},"5c0ed1c68c6b2",[],", but this is in a brand-new method that ",{"_key":686,"_type":72,"marks":687,"text":688},"5c0ed1c68c6b3",[74],"has no callers",{"_key":690,"_type":72,"marks":691,"text":692},"5c0ed1c68c6b4",[],". The code compiles and all tests pass—of course they do: no existing code changed.",[],{"_key":695,"_type":68,"children":696,"markDefs":701,"style":77},"a0712a8f5e41",[697],{"_key":698,"_type":72,"marks":699,"text":700},"a0712a8f5e410",[],"Commit the changes:",[],{"_key":703,"_type":272,"code":704,"markDefs":12},"66787c24b830","$ git add . && git commit\n[tactical-git 085e3ea] Add ReadReservations overload to SQL repo\n 1 file changed, 5 insertions(+)",{"_key":706,"_type":68,"children":707,"markDefs":727,"style":77},"295033087468",[708,712,716,720,723],{"_key":709,"_type":72,"marks":710,"text":711},"2950330874680",[],"This is a ",{"_key":713,"_type":72,"marks":714,"text":715},"2950330874681",[74],"save point",{"_key":717,"_type":72,"marks":718,"text":719},"2950330874682",[],". Saving your progress enables you to back out of this work if something else comes up. You don't have to push that commit anywhere. If you feel icky about that ",{"_key":721,"_type":72,"marks":722,"text":680},"2950330874683",[272],{"_key":724,"_type":72,"marks":725,"text":726},"2950330874684",[],", take comfort that it exists exclusively on your hard drive.",[],{"_key":729,"_type":68,"children":730,"markDefs":735,"style":77},"e27d81ed0cce",[731],{"_key":732,"_type":72,"marks":733,"text":734},"e27d81ed0cce0",[],"Moving from the old working state to the new working state took less than a minute.",[],{"_key":737,"_type":68,"children":738,"markDefs":767,"style":77},"d7ce552ec459",[739,743,747,750,754,758,763],{"_key":740,"_type":72,"marks":741,"text":742},"d7ce552ec4590",[],"The natural next step is to implement the new method. You may consider doing this incrementally as well, using TDD as you go, and committing after each ",{"_key":744,"_type":72,"marks":745,"text":746},"d7ce552ec4591",[74],"green",{"_key":748,"_type":72,"marks":749,"text":556},"d7ce552ec4592",[],{"_key":751,"_type":72,"marks":752,"text":753},"d7ce552ec4593",[74],"refactor",{"_key":755,"_type":72,"marks":756,"text":757},"d7ce552ec4594",[]," step (assuming you follow the ",{"_key":759,"_type":72,"marks":760,"text":762},"d7ce552ec4595",[761],"872d6bcc24e3","red-green-refactor checklist",{"_key":764,"_type":72,"marks":765,"text":766},"d7ce552ec4596",[],").",[768],{"_key":761,"_type":132,"href":769,"reference":12},"https://blog.ploeh.dk/2019/10/21/a-red-green-refactor-checklist",{"_key":771,"_type":68,"children":772,"markDefs":809,"style":77},"7e0b435d8a72",[773,777,780,784,789,793,798,801,805],{"_key":774,"_type":72,"marks":775,"text":776},"7e0b435d8a720",[],"I'm not going to do that here because I try to keep ",{"_key":778,"_type":72,"marks":779,"text":552},"7e0b435d8a721",[272],{"_key":781,"_type":72,"marks":782,"text":783},"7e0b435d8a722",[]," a ",{"_key":785,"_type":72,"marks":786,"text":788},"7e0b435d8a723",[787],"3aa9898251b0","Humble Object",{"_key":790,"_type":72,"marks":791,"text":792},"7e0b435d8a724",[],". The implementation will turn out to have a ",{"_key":794,"_type":72,"marks":795,"text":797},"7e0b435d8a725",[796],"39d633eaa641","cyclomatic complexity",{"_key":799,"_type":72,"marks":800,"text":111},"7e0b435d8a726",[],{"_key":802,"_type":72,"marks":803,"text":804},"7e0b435d8a727",[74],"2",{"_key":806,"_type":72,"marks":807,"text":808},"7e0b435d8a728",[],". Weighed against how much trouble it is to write and maintain a database integration test, I consider that sufficiently low to forgo adding a test (but if you disagree, nothing prevents you from adding tests in this step).",[810,812],{"_key":787,"_type":132,"href":811,"reference":12},"https://martinfowler.com/bliki/HumbleObject.html",{"_key":796,"_type":132,"href":813,"reference":12},"https://en.wikipedia.org/wiki/Cyclomatic_complexity",{"_key":815,"_type":272,"code":816,"markDefs":12},"eeb2bca1882c","public async Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(DateTime min, DateTime max)\n{\n    const string readByRangeSql = @\"\n        SELECT [PublicId], [Date], [Name], [Email], [Quantity]\n        FROM [dbo].[Reservations]\n        WHERE @Min \u003C= [Date] AND [Date] \u003C= @Max\";\n \n    var result = new List\u003CReservation>();\n \n    using var conn = new SqlConnection(ConnectionString);\n    using var cmd = new SqlCommand(readByRangeSql, conn);\n    cmd.Parameters.AddWithValue(\"@Min\", min);\n    cmd.Parameters.AddWithValue(\"@Max\", max);\n \n    await conn.OpenAsync().ConfigureAwait(false);\n    using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);\n    while (await rdr.ReadAsync().ConfigureAwait(false))\n        result.Add(\n            new Reservation(\n                (Guid)rdr[\"PublicId\"],\n                (DateTime)rdr[\"Date\"],\n                new Email((string)rdr[\"Email\"]),\n                new Name((string)rdr[\"Name\"]),\n                (int)rdr[\"Quantity\"]));\n \n    return result.AsReadOnly();\n}\n",{"_key":818,"_type":68,"children":819,"markDefs":832,"style":77},"452eeea0dcc8",[820,824,828],{"_key":821,"_type":72,"marks":822,"text":823},"452eeea0dcc80",[],"Granted, this takes more than a minute to write, but if you've done this kind of thing before, it probably takes less than ten—particularly if you've already figured the ",{"_key":825,"_type":72,"marks":826,"text":827},"452eeea0dcc81",[272],"SELECT",{"_key":829,"_type":72,"marks":830,"text":831},"452eeea0dcc82",[]," statement out on beforehand, perhaps by experimenting with a query editor.",[],{"_key":834,"_type":68,"children":835,"markDefs":840,"style":77},"c77df2c3dc56",[836],{"_key":837,"_type":72,"marks":838,"text":839},"c77df2c3dc560",[],"Once again, the code compiles and all tests pass. Commit:",[],{"_key":842,"_type":272,"code":843,"markDefs":12},"0b7bfb028a4c","$ git add . && git commit\n[tactical-git 6f1e07e] Implement ReadReservations overload in SQL repo\n 1 file changed, 25 insertions(+), 2 deletions(-)\n",{"_key":845,"_type":68,"children":846,"markDefs":851,"style":77},"533397a140b0",[847],{"_key":848,"_type":72,"marks":849,"text":850},"533397a140b00",[],"Status so far: We're two commits in, and all code works. The time spent coding between each commit has been short.",[],{"_key":853,"_type":68,"children":854,"markDefs":859,"style":179},"4fb763ecac32",[855],{"_key":856,"_type":72,"marks":857,"text":858},"4fb763ecac320",[],"Fake implementation",[],{"_key":861,"_type":68,"children":862,"markDefs":899,"style":77},"abd3855e23e2",[863,867,870,874,877,881,886,890,895],{"_key":864,"_type":72,"marks":865,"text":866},"abd3855e23e20",[],"The other class that implements ",{"_key":868,"_type":72,"marks":869,"text":349},"abd3855e23e21",[272],{"_key":871,"_type":72,"marks":872,"text":873},"abd3855e23e22",[]," is called ",{"_key":875,"_type":72,"marks":876,"text":560},"abd3855e23e23",[272],{"_key":878,"_type":72,"marks":879,"text":880},"abd3855e23e24",[],". It's a ",{"_key":882,"_type":72,"marks":883,"text":885},"abd3855e23e25",[884],"3aed45e5a813","Fake Object",{"_key":887,"_type":72,"marks":888,"text":889},"abd3855e23e26",[]," (a kind of ",{"_key":891,"_type":72,"marks":892,"text":894},"abd3855e23e27",[893],"090986bfc9a5","Test Double",{"_key":896,"_type":72,"marks":897,"text":898},"abd3855e23e28",[],") that exists only to support automated testing.",[900,902],{"_key":884,"_type":132,"href":901,"reference":12},"http://xunitpatterns.com/Fake%20Object.html",{"_key":893,"_type":132,"href":903,"reference":12},"https://en.wikipedia.org/wiki/Test_double",{"_key":905,"_type":68,"children":906,"markDefs":918,"style":77},"92122fbc7f41",[907,911,914],{"_key":908,"_type":72,"marks":909,"text":910},"92122fbc7f410",[],"The process for implementing the new method is exactly the same as for ",{"_key":912,"_type":72,"marks":913,"text":552},"92122fbc7f411",[272],{"_key":915,"_type":72,"marks":916,"text":917},"92122fbc7f412",[],". First, add the method:",[],{"_key":920,"_type":272,"code":669,"markDefs":12},"1095e6e8d056",{"_key":922,"_type":68,"children":923,"markDefs":928,"style":77},"af617d24774b",[924],{"_key":925,"_type":72,"marks":926,"text":927},"af617d24774b0",[],"The code compiles and all tests pass. Commit:",[],{"_key":930,"_type":272,"code":931,"markDefs":12},"c9799e9dcdb6","$ git add . && git commit\n[tactical-git c5d3fba] Add ReadReservations overload to FakeDatabase\n 1 file changed, 5 insertions(+)\n",{"_key":933,"_type":68,"children":934,"markDefs":939,"style":77},"45f6dcab3162",[935],{"_key":936,"_type":72,"marks":937,"text":938},"45f6dcab31620",[],"Then add the implementation:",[],{"_key":941,"_type":272,"code":942,"markDefs":12},"6c121c34a672","public Task\u003CIReadOnlyCollection\u003CReservation>> ReadReservations(DateTime min, DateTime max)\n{\n    return Task.FromResult\u003CIReadOnlyCollection\u003CReservation>>(\n        this.Where(r => min \u003C= r.At && r.At \u003C= max).ToList());\n}\n",{"_key":944,"_type":68,"children":945,"markDefs":949,"style":77},"6465241b5999",[946],{"_key":947,"_type":72,"marks":948,"text":927},"6465241b59990",[],[],{"_key":951,"_type":272,"code":952,"markDefs":12},"f462b8e254c0","$ git add . && git commit\n[tactical-git e258575] Implement FakeDatabase.ReadReservations overload\n 1 file changed, 2 insertions(+), 1 deletion(-)\n",{"_key":954,"_type":68,"children":955,"markDefs":960,"style":77},"8aa40dfc9506",[956],{"_key":957,"_type":72,"marks":958,"text":959},"8aa40dfc95060",[],"Each of these commits represent only a few minutes of programming time; that's the whole point. By committing often, you have granular save points you can retreat to if things start to go wrong.",[],{"_key":962,"_type":68,"children":963,"markDefs":968,"style":179},"23b007e160d6",[964],{"_key":965,"_type":72,"marks":966,"text":967},"23b007e160d60",[],"Now change the interface",[],{"_key":970,"_type":68,"children":971,"markDefs":983,"style":77},"55c7ee6a0de4",[972,976,979],{"_key":973,"_type":72,"marks":974,"text":975},"55c7ee6a0de40",[],"Keep in mind that we've been adding the methods in anticipation that the ",{"_key":977,"_type":72,"marks":978,"text":349},"55c7ee6a0de41",[272],{"_key":980,"_type":72,"marks":981,"text":982},"55c7ee6a0de42",[]," interface will change. It hasn't changed yet, remember. I stashed that edit.",[],{"_key":985,"_type":68,"children":986,"markDefs":1003,"style":77},"2955f2901a48",[987,991,994,997,1000],{"_key":988,"_type":72,"marks":989,"text":990},"2955f2901a480",[],"The new method is now in place everywhere it needs to be in place: both on ",{"_key":992,"_type":72,"marks":993,"text":552},"2955f2901a481",[272],{"_key":995,"_type":72,"marks":996,"text":556},"2955f2901a482",[],{"_key":998,"_type":72,"marks":999,"text":560},"2955f2901a483",[272],{"_key":1001,"_type":72,"marks":1002,"text":129},"2955f2901a484",[],[],{"_key":1005,"_type":68,"children":1006,"markDefs":1011,"style":77},"61aea2c06147",[1007],{"_key":1008,"_type":72,"marks":1009,"text":1010},"61aea2c061470",[],"Now pop the stash:",[],{"_key":1013,"_type":272,"code":1014,"markDefs":12},"d41545367494","$ git stash pop\nOn branch tactical-git\nChanges not staged for commit:\n  (use \"git add \u003Cfile>...\" to update what will be committed)\n  (use \"git restore \u003Cfile>...\" to discard changes in working directory)\n        modified:   Restaurant.RestApi/IReservationsRepository.cs\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nDropped refs/stash@{0} (4703ba9e2bca72aeafa11f859577b478ff406ff9)\n",{"_key":1016,"_type":68,"children":1017,"markDefs":1029,"style":77},"d524b4efd177",[1018,1022,1025],{"_key":1019,"_type":72,"marks":1020,"text":1021},"d524b4efd1770",[],"This re-adds the ",{"_key":1023,"_type":72,"marks":1024,"text":302},"d524b4efd1771",[272],{"_key":1026,"_type":72,"marks":1027,"text":1028},"d524b4efd1772",[]," method overload to the interface. When I first tried to do this, the code didn't compile because the classes that implement the interface didn't have that method.",[],{"_key":1031,"_type":68,"children":1032,"markDefs":1037,"style":77},"94472b422d06",[1033],{"_key":1034,"_type":72,"marks":1035,"text":1036},"94472b422d060",[],"Now, on the other hand, the code immediately compiles and all tests pass. Commit.",[],{"_key":1039,"_type":272,"code":1040,"markDefs":12},"c3824a4a211c","$ git add . && git commit\n[tactical-git de440df] Add ReadReservations overload to repo interface\n 1 file changed, 2 insertions(+)\n",{"_key":1042,"_type":68,"children":1043,"markDefs":1056,"style":77},"33f4762eb5fc",[1044,1048,1052],{"_key":1045,"_type":72,"marks":1046,"text":1047},"33f4762eb5fc0",[],"We're done. By a tactical application of ",{"_key":1049,"_type":72,"marks":1050,"text":1051},"33f4762eb5fc1",[272],"git stash",{"_key":1053,"_type":72,"marks":1054,"text":1055},"33f4762eb5fc2",[],", it was possible to partition what looked like one long, unsafe maneuver into five smaller, safer steps.",[],{"_key":1058,"_type":68,"children":1059,"markDefs":1064,"style":179},"0c4b23ac6a64",[1060],{"_key":1061,"_type":72,"marks":1062,"text":1063},"0c4b23ac6a640",[],"Tactical Git",[],{"_key":1066,"_type":68,"children":1067,"markDefs":1072,"style":77},"5fdf156067a7",[1068],{"_key":1069,"_type":72,"marks":1070,"text":1071},"5fdf156067a70",[],"Someone once, in passing, mentioned that one should never be more than five minutes away from a commit. That's the same kind of idea. When you begin editing code, do yourself the favor of moving in such a way that you can get to a new working state in five minutes.",[],{"_key":1074,"_type":68,"children":1075,"markDefs":1088,"style":77},"9ba88c56d0be",[1076,1080,1084],{"_key":1077,"_type":72,"marks":1078,"text":1079},"9ba88c56d0be0",[],"This doesn't mean that you have to commit ",{"_key":1081,"_type":72,"marks":1082,"text":1083},"9ba88c56d0be1",[74],"every",{"_key":1085,"_type":72,"marks":1086,"text":1087},"9ba88c56d0be2",[]," five minutes. It's okay to take time to think. Sometimes, I go for a run, or go grocery shopping, to allow my brain to chew on a problem. Sometimes, I just sit and look at the code without typing anything. And sometimes, I start editing the code without a good plan, and that's okay, too... Often, by dawdling with the code, inspiration comes to me.",[],{"_key":1090,"_type":68,"children":1091,"markDefs":1096,"style":77},"49349f261359",[1092],{"_key":1093,"_type":72,"marks":1094,"text":1095},"49349f2613590",[],"When that happens, the code may be in some inconsistent state. Perhaps it compiles; perhaps it doesn't. It's okay. I can always reset to my latest save point. Often, I reset by stashing the results of my half-baked experimentation. That way, I don't throw anything away that may turn out to be valuable, but I still get to start with a clean slate.",[],{"_key":1098,"_type":68,"children":1099,"markDefs":1104,"style":77},"34c5573218a4",[1100],{"_key":1101,"_type":72,"marks":1102,"text":1103},"34c5573218a40",[],"git stash is probably the command I use the most for increased maneuverability. After that, being able to move between branches locally is also useful. Sometimes, I do a quick-and-dirty prototype in one branch. Once I feel that I understand the direction in which I must go, I commit to that branch, reset my work to a more proper commit, make a new branch and do the work again, but now with tests or other things that I skipped during the prototype.",[],{"_key":1106,"_type":68,"children":1107,"markDefs":1128,"style":77},"530b4a5543d3",[1108,1112,1116,1120,1124],{"_key":1109,"_type":72,"marks":1110,"text":1111},"530b4a5543d30",[],"Being able to stash changes is also great when you discover that the code you're writing right now needs something else to be in place (e.g. a helper method that doesn't yet exist). Stash the changes, add the thing you just learned about, commit that, and the pop the stash. Subsection ",{"_key":1113,"_type":72,"marks":1114,"text":1115},"530b4a5543d31",[74],"11.1.3 Separate Refactoring of Test and Production Code",{"_key":1117,"_type":72,"marks":1118,"text":1119},"530b4a5543d32",[]," in ",{"_key":1121,"_type":72,"marks":1122,"text":285},"530b4a5543d33",[1123],"96082d55dacb",{"_key":1125,"_type":72,"marks":1126,"text":1127},"530b4a5543d34",[]," contains an example of that.",[1129],{"_key":1123,"_type":132,"href":309,"reference":12},{"_key":1131,"_type":68,"children":1132,"markDefs":1154,"style":77},"5cf4e0400b30",[1133,1137,1141,1145,1150],{"_key":1134,"_type":72,"marks":1135,"text":1136},"5cf4e0400b300",[],"I also use ",{"_key":1138,"_type":72,"marks":1139,"text":1140},"5cf4e0400b301",[272],"git rebase",{"_key":1142,"_type":72,"marks":1143,"text":1144},"5cf4e0400b302",[]," a lot. While ",{"_key":1146,"_type":72,"marks":1147,"text":1149},"5cf4e0400b303",[1148],"1a5c2f8d8a50","I'm no fan of squashing commits",{"_key":1151,"_type":72,"marks":1152,"text":1153},"5cf4e0400b304",[],", I've no compunction about reordering commits on my local Git branches. As long as I haven't shared the commits with the world, rewriting history can be beneficial.",[1155],{"_key":1148,"_type":132,"href":1156,"reference":12},"https://blog.ploeh.dk/2020/10/05/fortunately-i-dont-squash-my-commits",{"_key":1158,"_type":68,"children":1159,"markDefs":1164,"style":77},"8f7276dbd159",[1160],{"_key":1161,"_type":72,"marks":1162,"text":1163},"8f7276dbd1590",[],"Git enables you to experiment, to try out one direction, and to back out if the direction begins to look like a dead end. Just stash or commit your changes, move back to a previous save point and try an alternative direction. Keep in mind that you can leave as many incomplete branches on your hard drive as you like. You don't have to push them anywhere.",[],{"_key":1166,"_type":68,"children":1167,"markDefs":1180,"style":77},"95ffb7ef580c",[1168,1172,1176],{"_key":1169,"_type":72,"marks":1170,"text":1171},"95ffb7ef580c0",[],"That's what I consider ",{"_key":1173,"_type":72,"marks":1174,"text":1175},"95ffb7ef580c1",[74],"tactical use of Git",{"_key":1177,"_type":72,"marks":1178,"text":1179},"95ffb7ef580c2",[],". It's maneuvers you perform to be productive in the small. The artifacts of these moves remain on your local hard drive, unless you explicitly choose to share them with others.",[],{"_key":1182,"_type":68,"children":1183,"markDefs":1188,"style":179},"615bc521a181",[1184],{"_key":1185,"_type":72,"marks":1186,"text":1187},"615bc521a1810",[],"Conclusion",[],{"_key":1190,"_type":68,"children":1191,"markDefs":1210,"style":77},"c3a79aba670a",[1192,1196,1200,1203,1207],{"_key":1193,"_type":72,"marks":1194,"text":1195},"c3a79aba670a0",[],"Git is a tool with more potential than most people realize. Usually, programmers use it to synchronize their work with others. Thus, they use it only when they feel the need to do that. That's ",{"_key":1197,"_type":72,"marks":1198,"text":1199},"c3a79aba670a1",[272],"git push",{"_key":1201,"_type":72,"marks":1202,"text":556},"c3a79aba670a2",[],{"_key":1204,"_type":72,"marks":1205,"text":1206},"c3a79aba670a3",[272],"git pull",{"_key":1208,"_type":72,"marks":1209,"text":129},"c3a79aba670a4",[],[],{"_key":1212,"_type":68,"children":1213,"markDefs":1218,"style":77},"029723610759",[1214],{"_key":1215,"_type":72,"marks":1216,"text":1217},"0297236107590",[],"While that's a useful and essential feature of Git, if that's all you do, you might as well use a centralized source control system.",[],{"_key":1220,"_type":68,"children":1221,"markDefs":1226,"style":77},"595572488206",[1222],{"_key":1223,"_type":72,"marks":1224,"text":1225},"5955724882060",[],"The value of Git is the tactical advantage it also provides. You can use it to experiment, make mistakes, flail, and struggle on your local machine, and at any time, you can just reset if things get too hard.",[],{"_key":1228,"_type":68,"children":1229,"markDefs":1234,"style":77},"4ed8369ce1f9",[1230],{"_key":1231,"_type":72,"marks":1232,"text":1233},"4ed8369ce1f90",[],"In this article, you saw an example of adding an interface method, only to realize that this involves more work than you may have initially thought. Instead of just pushing through on an ill-planned unsafe maneuver that has no clear end, just back out by stashing the changes so far. Then move deliberately in smaller steps and finally pop the stash.",[],{"_key":1236,"_type":68,"children":1237,"markDefs":1242,"style":77},"aba4ed4744f3",[1238],{"_key":1239,"_type":72,"marks":1240,"text":1241},"aba4ed4744f30",[],"Just like a rock climber like Alex Honnold trains with ropes and harness, Git enables you to proceed in small steps with fallback options. Use it to your advantage.",[],true,"2022/12/19","How you can use micro-commits to effectively apply the Strangler Fig pattern.",{"_type":56,"asset":1247},{"_ref":1248,"_type":59},"image-72e1adb75dd66a437fc120f6da5e77f64c5f5145-2400x1260-jpg",{"code":1250,"language":1251},"\u003C!-- wp:paragraph -->\n\u003Cp>\u003Cem>[Ed. note: While we take some time to rest up over the holidays and prepare for next year, we are re-publishing our top ten posts for the year. Please enjoy our favorite work this year and we’ll see you in 2023.]\u003C/em>\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In the movie \u003Ca href=\"https://en.wikipedia.org/wiki/Free_Solo\">Free Solo\u003C/a> the rock climber \u003Ca href=\"https://en.wikipedia.org/wiki/Alex_Honnold\">Alex Honnold\u003C/a> trains to perform a \u003Ca href=\"https://en.wikipedia.org/wiki/Free_solo_climbing\">free solo climb\u003C/a> of \u003Ca href=\"https://en.wikipedia.org/wiki/El_Capitan\">El Capitan\u003C/a>, a mountain in \u003Ca href=\"https://en.wikipedia.org/wiki/Yosemite_National_Park\">Yosemite\u003C/a>.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https://lh6.googleusercontent.com/AyHGvbCgBksn0jbUyG0hthGUaRcYHpGu9gf_Z_9JRO3NmPeMg0qjus_6SbAFmR1c13559r2jVNyxDmijFxlhw8pc1Nb43chY8nUGGdIdDIyWHk5ZqE14cQwMNIYtv7z8li2DD6sk\" alt=\"\"/>\u003Cfigcaption class=\"wp-element-caption\">(El Capitan. \u003Ca href=\"https://commons.wikimedia.org/wiki/File:Yosemite_El_Capitan.jpg\">Photo by Mike Murphy, 2005\u003C/a>.)\u003C/figcaption>\u003C/figure>\n\u003C!-- /wp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>It's a good movie, but if you haven't seen it, \u003Cem>free solo climbing\u003C/em> is when you scale a rock face without ropes, harness, or safety equipment. If you lose your grip and fall, you'll die. El Capitan, just to rub it in, is 914 meters of vertical rock. Free-climbing it is an incredible endeavor, but Honnold gets it done by \u003Cem>committing \u003C/em>to one move at a time (this article is about using Git, after all).&nbsp;\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-save-point\">Save point\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Honnold didn't just free-climb El Capitan. He trained deliberately towards the goal of free climbing El Capitan.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The documentary shows how he repeatedly climbs El Capitan \u003Cem>with\u003C/em> safety equipment. He plans a route and climbs it several times. On each of the training ascensions, he's using ropes, a harness, and various fasteners for the ropes. When he falls during training, he doesn't fall far, because the rope, harness, and fasteners stop the fall at the last point of fixation.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>It's almost like a \u003Ca href=\"https://en.wikipedia.org/wiki/Saved_game\">video game save point\u003C/a>.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In one memorable scene, Honnold considers a jump from one position to another. Hundreds of meters in the air, parallel to a vertical rock face. It's a truly precarious maneuver. If he fails, he'll die.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Or, that's true for the free climb. At first, he rehearses the move using rope and harness. This enables him to perform a potentially fatal jump in relative safety. When it goes wrong, he's back at where he fixed his rope, and he may try again.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When you’re making large code changes, even migrating to a new implementation, you can create save points to prevent catastrophes. Like Alex Honold, you can fix your code in place to give you a better chance to get to the next successful build.&nbsp;&nbsp;\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-precarious-editing\">Precarious editing\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When you edit code, you go from one working state to another, but during the process, the code doesn't always run or compile.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Consider an interface like this:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public interface IReservationsRepository\n{\n    Task Create(Reservation reservation);\n \n    Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(\n        DateTime dateTime);\n \n    Task&lt;Reservation?&gt; ReadReservation(Guid id);\n \n    Task Update(Reservation reservation);\n \n    Task Delete(Guid id);\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This, as most of the code in this article, is from my book \u003Ca href=\"https://blog.ploeh.dk/code-that-fits-in-your-head\">Code That Fits in Your Head\u003C/a>. As I describe in the section on the \u003Ca href=\"https://martinfowler.com/bliki/StranglerFigApplication.html\">Strangler Fig pattern\u003C/a>, at one point I had to add a new method to the interface. The new method should be an overload of the \u003Ccode>ReadReservations\u003C/code> method with this signature:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max);\u003C/code>\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Once you start typing that method definition, however, your code no longer works:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(\n    DateTime dateTime);\n \nT\n \nTask&lt;\u003Cspan style=\"text-decoration-line: underline  !important;\ntext-decoration-style: wavy   !important;\ntext-decoration-color: red  !important;\">Reservation?\u003C/span>&gt; \u003Cspan style=\"text-decoration-line: underline  !important;\ntext-decoration-style: wavy   !important;\ntext-decoration-color: red  !important;\">ReadReservation\u003C/span>(Guid id);\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>If you're editing in Visual Studio, it'll immediately light up with red squiggly underlines, indicating that the code doesn't parse.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You have to type the entire method declaration before the red squiggly lines disappear, but even then, the code doesn't compile. While the interface definition may be syntactically valid, adding the new method broke some other code. The code base contains classes that implement the \u003Ccode>IReservationsRepository\u003C/code> interface, but none of them define the method you just added. The compiler knows this, and complains:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:quote -->\n\u003Cblockquote class=\"wp-block-quote\">\u003C!-- wp:paragraph -->\n\u003Cp>Error CS0535 'SqlReservationsRepository' does not implement interface member 'IReservationsRepository.ReadReservations(DateTime, DateTime)'\u003C/p>\n\u003C!-- /wp:paragraph -->\u003C/blockquote>\n\u003C!-- /wp:quote -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp id=\"h-there-s-nothing-wrong-with-that-i-m-just-trying-to-highlight-how-editing-code-involves-a-transition-between-two-working-states\">There's nothing wrong with that. I'm just trying to highlight how editing code involves a transition between two working states:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https://lh6.googleusercontent.com/MG3WYGF56Y9qGZ86meWq6VP3cRUR09mNsECttj_-J9kQw-0eNX3dZ3x5L8egVwrxfZbsiM5kbsMcnWaWOCu1SETDp2tKUDPTeF8XiT7QBXAVmKxfIGlz0_e8LoBJmYAhBlA9k-X1\" alt=\"\"/>\u003C/figure>\n\u003C!-- /wp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In \u003Cem>Free Solo\u003C/em> the entire climb is dangerous, but there's a particularly perilous maneuver that Alex Honnold has to make because he can't find a safer route. For most of the climb, he climbs using safer techniques, moving from position to position in small increments, never losing grip or footing as he shifts his center of gravity.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>There's a reason he favors climbing like that. It's safer.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-micro-commits\">Micro-commits\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You can't edit code without temporarily breaking it. What you can do, however, is move in small, deliberate steps. Every time you reach a point where the code compiles and all tests pass: commit the changes to Git.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Tim Ottinger calls this a \u003Ca href=\"https://www.industriallogic.com/blog/whats-this-about-micro-commits/\">micro-commit\u003C/a>. Not only should you commit every time you have a green bar—you should deliberately move in such a way that the distance between two commits is as short as possible. If you can think of alternative ways to change the code, choose the pathway that promises the smallest steps.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Why make dangerous leaps when you can advance in small, controlled moves?\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:image -->\n\u003Cfigure class=\"wp-block-image\">\u003Cimg src=\"https://lh3.googleusercontent.com/B423apacO22lIXT2lPSfAJkpusgABGa0aqg6fSJZ5iFaCSvuZYFrzUH5CERwXqs25nWrSVznIvgoeNg9W3cxVXmzVW-cQfREPuVwwVi9zS7MLP1fFas8J3zMLa0iSCJfl_JtKFhI\" alt=\"\"/>\u003C/figure>\n\u003C!-- /wp:image -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Git is a wonderful tool for maneuverability. Most people don't think of it like that. They start programming, and hours later, they may commit to Git in order to push a branch out.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Tim Ottinger doesn't do that, and neither do I. I use Git tactically.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I'll walk you through an example.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-adding-an-interface-method\">Adding an interface method\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>As described above, I wanted to add a \u003Ccode>ReadReservations\u003C/code> overload to the \u003Ccode>IReservationsRepository\u003C/code> interface. The motivation for that is described in \u003Ca href=\"https://blog.ploeh.dk/code-that-fits-in-your-head\">Code That Fits in Your Head\u003C/a>, but that's not the point here. The point is to use Git to move in small increments.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When you add a new method to an existing interface, the code base fails to compile when you have existing classes that implement that interface. How do you deal with that situation?&nbsp; Do you just forge ahead and implement the new method? Or are there alternatives?\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Here's an alternative path that moves in smaller increments.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>First, \u003Cem>lean on the compiler\u003C/em> (as \u003Ca href=\"https://blog.ploeh.dk/ref/wewlc\">Working Effectively with Legacy Code\u003C/a> puts it). The compiler errors tell you which classes lack the new method. In the example code base, it's \u003Ccode>SqlReservationsRepository\u003C/code> and \u003Ccode>FakeDatabase\u003C/code>. Open one of those code files, but don't do anything yet. Instead, copy the new \u003Ccode>ReadReservations\u003C/code> method declaration to the clipboard. Then stash the changes:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>$ git stash\u003C/code>\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>\u003Ccode>Saved working directory and index state WIP on tactical-git: [...]\u003C/code>\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The code is now back in a working state. Now find a good place to add the new method to one of the classes that implement the interface.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-sql-implementation\">SQL implementation\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I’ll start with the \u003Ccode>SqlReservationsRepository\u003C/code> class. Once I've navigated to the line in the file where I want to add the new method, I paste in the method declaration:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>\u003Ccode>Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max);\u003C/code>\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>That doesn't compile because the method ends with a semicolon and has no body.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>So I make the method \u003Ccode>public\u003C/code>, delete the semicolon, and add curly brackets:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; \u003Cspan style=\"text-decoration-line: underline  !important; text-decoration-style: wavy   !important; text-decoration-color: red  !important;\">ReadReservations\u003C/span>(DateTime min, DateTime max)\n{\n\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This still doesn't compile, because the method declaration promises to return a value, but the body is empty.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>What's the shortest way to a working system?\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max)\n{\n    throw new NotImplementedException();\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>You may not want to commit code that throws \u003Ccode>NotImplementedException\u003C/code>, but this is in a brand-new method that \u003Cem>has no callers\u003C/em>. The code compiles and all tests pass—of course they do: no existing code changed.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Commit the changes:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git add . &amp;&amp; git commit\n&#91;tactical-git 085e3ea] Add ReadReservations overload to SQL repo\n 1 file changed, 5 insertions(+)\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This is a \u003Cem>save point\u003C/em>. Saving your progress enables you to back out of this work if something else comes up. You don't have to push that commit anywhere. If you feel icky about that \u003Ccode>NotImplementedException\u003C/code>, take comfort that it exists exclusively on your hard drive.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Moving from the old working state to the new working state took less than a minute.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The natural next step is to implement the new method. You may consider doing this incrementally as well, using TDD as you go, and committing after each \u003Cem>green\u003C/em> and \u003Cem>refactor\u003C/em> step (assuming you follow the \u003Ca href=\"https://blog.ploeh.dk/2019/10/21/a-red-green-refactor-checklist\">red-green-refactor checklist\u003C/a>).\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I'm not going to do that here because I try to keep \u003Ccode>SqlReservationsRepository\u003C/code> a \u003Ca href=\"https://martinfowler.com/bliki/HumbleObject.html\">Humble Object\u003C/a>. The implementation will turn out to have a \u003Ca href=\"https://en.wikipedia.org/wiki/Cyclomatic_complexity\">cyclomatic complexity\u003C/a> of \u003Cem>2\u003C/em>. Weighed against how much trouble it is to write and maintain a database integration test, I consider that sufficiently low to forgo adding a test (but if you disagree, nothing prevents you from adding tests in this step).\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public async Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max)\n{\n    const string readByRangeSql = @\"\n        SELECT &#91;PublicId], &#91;Date], &#91;Name], &#91;Email], &#91;Quantity]\n        FROM &#91;dbo].&#91;Reservations]\n        WHERE @Min &lt;= &#91;Date] AND &#91;Date] &lt;= @Max\";\n \n    var result = new List&lt;Reservation&gt;();\n \n    using var conn = new SqlConnection(ConnectionString);\n    using var cmd = new SqlCommand(readByRangeSql, conn);\n    cmd.Parameters.AddWithValue(\"@Min\", min);\n    cmd.Parameters.AddWithValue(\"@Max\", max);\n \n    await conn.OpenAsync().ConfigureAwait(false);\n    using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);\n    while (await rdr.ReadAsync().ConfigureAwait(false))\n        result.Add(\n            new Reservation(\n                (Guid)rdr&#91;\"PublicId\"],\n                (DateTime)rdr&#91;\"Date\"],\n                new Email((string)rdr&#91;\"Email\"]),\n                new Name((string)rdr&#91;\"Name\"]),\n                (int)rdr&#91;\"Quantity\"]));\n \n    return result.AsReadOnly();\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Granted, this takes more than a minute to write, but if you've done this kind of thing before, it probably takes less than ten—particularly if you've already figured the \u003Ccode>SELECT\u003C/code> statement out on beforehand, perhaps by experimenting with a query editor.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Once again, the code compiles and all tests pass. Commit:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git add . &amp;&amp; git commit\n&#91;tactical-git 6f1e07e] Implement ReadReservations overload in SQL repo\n 1 file changed, 25 insertions(+), 2 deletions(-)\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Status so far: We're two commits in, and all code works. The time spent coding between each commit has been short.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-fake-implementation\">Fake implementation\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The other class that implements \u003Ccode>IReservationsRepository\u003C/code> is called \u003Ccode>FakeDatabase\u003C/code>. It's a \u003Ca href=\"http://xunitpatterns.com/Fake%20Object.html\">Fake Object\u003C/a> (a kind of \u003Ca href=\"https://en.wikipedia.org/wiki/Test_double\">Test Double\u003C/a>) that exists only to support automated testing.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The process for implementing the new method is exactly the same as for \u003Ccode>SqlReservationsRepository\u003C/code>. First, add the method:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max)\n{\n    throw new NotImplementedException();\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The code compiles and all tests pass. Commit:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git add . &amp;&amp; git commit\n&#91;tactical-git c5d3fba] Add ReadReservations overload to FakeDatabase\n 1 file changed, 5 insertions(+)\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Then add the implementation:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>public Task&lt;IReadOnlyCollection&lt;Reservation&gt;&gt; ReadReservations(DateTime min, DateTime max)\n{\n    return Task.FromResult&lt;IReadOnlyCollection&lt;Reservation&gt;&gt;(\n        this.Where(r =&gt; min &lt;= r.At &amp;&amp; r.At &lt;= max).ToList());\n}\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The code compiles and all tests pass. Commit:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git add . &amp;&amp; git commit\n&#91;tactical-git e258575] Implement FakeDatabase.ReadReservations overload\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Each of these commits represent only a few minutes of programming time; that's the whole point. By committing often, you have granular save points you can retreat to if things start to go wrong.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-now-change-the-interface\">Now change the interface\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Keep in mind that we've been adding the methods in anticipation that the \u003Ccode>IReservationsRepository\u003C/code> interface will change. It hasn't changed yet, remember. I stashed that edit.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The new method is now in place everywhere it needs to be in place: both on \u003Ccode>SqlReservationsRepository\u003C/code> and \u003Ccode>FakeDatabase\u003C/code>.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Now pop the stash:\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git stash pop\nOn branch tactical-git\nChanges not staged for commit:\n  (use \"git add &lt;file&gt;...\" to update what will be committed)\n  (use \"git restore &lt;file&gt;...\" to discard changes in working directory)\n        modified:   Restaurant.RestApi/IReservationsRepository.cs\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\nDropped refs/stash@{0} (4703ba9e2bca72aeafa11f859577b478ff406ff9)\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This re-adds the \u003Ccode>ReadReservations\u003C/code> method overload to the interface. When I first tried to do this, the code didn't compile because the classes that implement the interface didn't have that method.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Now, on the other hand, the code immediately compiles and all tests pass. Commit.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:code -->\n\u003Cpre class=\"wp-block-code\">\u003Ccode>$ git add . &amp;&amp; git commit\n&#91;tactical-git de440df] Add ReadReservations overload to repo interface\n 1 file changed, 2 insertions(+)\n\u003C/code>\u003C/pre>\n\u003C!-- /wp:code -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>We're done. By a tactical application of \u003Ccode>git stash\u003C/code>, it was possible to partition what looked like one long, unsafe maneuver into five smaller, safer steps.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-tactical-git\">Tactical Git\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Someone once, in passing, mentioned that one should never be more than five minutes away from a commit. That's the same kind of idea. When you begin editing code, do yourself the favor of moving in such a way that you can get to a new working state in five minutes.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>This doesn't mean that you have to commit \u003Cem>every\u003C/em> five minutes. It's okay to take time to think. Sometimes, I go for a run, or go grocery shopping, to allow my brain to chew on a problem. Sometimes, I just sit and look at the code without typing anything. And sometimes, I start editing the code without a good plan, and that's okay, too... Often, by dawdling with the code, inspiration comes to me.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>When that happens, the code may be in some inconsistent state. Perhaps it compiles; perhaps it doesn't. It's okay. I can always reset to my latest save point. Often, I reset by stashing the results of my half-baked experimentation. That way, I don't throw anything away that may turn out to be valuable, but I still get to start with a clean slate.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>git stash is probably the command I use the most for increased maneuverability. After that, being able to move between branches locally is also useful. Sometimes, I do a quick-and-dirty prototype in one branch. Once I feel that I understand the direction in which I must go, I commit to that branch, reset my work to a more proper commit, make a new branch and do the work again, but now with tests or other things that I skipped during the prototype.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Being able to stash changes is also great when you discover that the code you're writing right now needs something else to be in place (e.g. a helper method that doesn't yet exist). Stash the changes, add the thing you just learned about, commit that, and the pop the stash. Subsection \u003Cem>11.1.3 Separate Refactoring of Test and Production Code\u003C/em> in \u003Ca href=\"https://blog.ploeh.dk/code-that-fits-in-your-head\">Code That Fits in Your Head\u003C/a> contains an example of that.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>I also use \u003Ccode>git rebase\u003C/code> a lot. While \u003Ca href=\"https://blog.ploeh.dk/2020/10/05/fortunately-i-dont-squash-my-commits\">I'm no fan of squashing commits\u003C/a>, I've no compunction about reordering commits on my local Git branches. As long as I haven't shared the commits with the world, rewriting history can be beneficial.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Git enables you to experiment, to try out one direction, and to back out if the direction begins to look like a dead end. Just stash or commit your changes, move back to a previous save point and try an alternative direction. Keep in mind that you can leave as many incomplete branches on your hard drive as you like. You don't have to push them anywhere.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>That's what I consider \u003Cem>tactical use of Git\u003C/em>. It's maneuvers you perform to be productive in the small. The artifacts of these moves remain on your local hard drive, unless you explicitly choose to share them with others.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:heading {\"level\":1} -->\n\u003Ch1 id=\"h-conclusion\">Conclusion&nbsp;\u003C/h1>\n\u003C!-- /wp:heading -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Git is a tool with more potential than most people realize. Usually, programmers use it to synchronize their work with others. Thus, they use it only when they feel the need to do that. That's \u003Ccode>git push\u003C/code> and \u003Ccode>git pull\u003C/code>.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>While that's a useful and essential feature of Git, if that's all you do, you might as well use a centralized source control system.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>The value of Git is the tactical advantage it also provides. You can use it to experiment, make mistakes, flail, and struggle on your local machine, and&nbsp; at any time, you can just reset if things get too hard.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>In this article, you saw an example of adding an interface method, only to realize that this involves more work than you may have initially thought. Instead of just pushing through on an ill-planned unsafe maneuver that has no clear end, just back out by stashing the changes so far. Then move deliberately in smaller steps and finally pop the stash.\u003C/p>\n\u003C!-- /wp:paragraph -->\n\n\u003C!-- wp:paragraph -->\n\u003Cp>Just like a rock climber like Alex Honnold trains with ropes and harness, Git enables you to proceed in small steps with fallback options. Use it to your advantage.\u003C/p>\n\u003C!-- /wp:paragraph -->","html","2022-12-19T15:00:00.000Z",{"current":1254},"use-git-tactically",[1256,1264,1268,1272],{"_createdAt":1257,"_id":1258,"_rev":1259,"_type":1260,"_updatedAt":1257,"slug":1261,"title":1263},"2023-05-23T16:43:21Z","wp-tagcat-code-for-a-living","9HpbCsT2tq0xwozQfkc4ih","blogTag",{"current":1262},"code-for-a-living","Code for a Living",{"_createdAt":1257,"_id":1265,"_rev":1259,"_type":1260,"_updatedAt":1257,"slug":1266,"title":1267},"wp-tagcat-git",{"current":1267},"git",{"_createdAt":1257,"_id":1269,"_rev":1259,"_type":1260,"_updatedAt":1257,"slug":1270,"title":1271},"wp-tagcat-micro-commits",{"current":1271},"micro-commits",{"_createdAt":1257,"_id":1273,"_rev":1259,"_type":1260,"_updatedAt":1257,"slug":1274,"title":1276},"wp-tagcat-strangler-fig-pattern",{"current":1275},"strangler-fig-pattern","strangler fig pattern","Use Git tactically",[1279,1285,1291,1297],{"_id":1280,"publishedAt":1281,"slug":1282,"sponsored":12,"title":1284},"28e560af-f0aa-4d46-bd90-f435ad604aa7","2026-06-26T14:00:27.102Z",{"_type":10,"current":1283},"paging-charity-how-can-engineering-leaders-avoid-becoming-bond-villains","Paging Charity! How can engineering leaders avoid becoming Bond villains?",{"_id":1286,"publishedAt":1287,"slug":1288,"sponsored":12,"title":1290},"4b22c2a3-3779-4966-93eb-5230391dbdce","2026-06-23T14:08:58.595Z",{"_type":10,"current":1289},"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":1292,"publishedAt":1293,"slug":1294,"sponsored":12,"title":1296},"5cf362e1-fe7b-45af-b69c-914731c6a052","2026-06-23T14:00:00.000Z",{"_type":10,"current":1295},"the-2026-developer-survey-is-now-open-for-human-developers-only","The 2026 Developer Survey is now open (for human developers only)!",{"_id":1298,"publishedAt":1299,"slug":1300,"sponsored":12,"title":1302},"30b995f7-7cb9-4dd8-bf71-d0685940a32b","2026-06-19T14:00:00.000Z",{"_type":10,"current":1301},"dispatches-from-o-reilly-from-capabilities-to-responsibilities","Dispatches from O'Reilly: From capabilities to responsibilities",{"data":1304,"sourceMap":-1},{"count":1305,"lastTimestamp":1306},45,"2023-05-25T09:47:55Z"]