go

golang的问题

Golang sucks

Posted by Zeusro on April 19, 2026
👈🏻 Select language

一句话概括,golang是一种高性能网络编程语言,仅此而已。

零值陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type AllBasicTypes struct {
	B    bool
	Str  string

	I8   int8
	I16  int16
	I32  int32
	I64  int64
	I    int

	U8   uint8
	U16  uint16
	U32  uint32
	U64  uint64
	U    uint
	Up   uintptr

	By   byte // uint8
	Ru   rune // int32

	F32  float32
	F64  float64

	C64  complex64
	C128 complex128
}

// var v AllBasicTypes —— 各字段为零值:false、""、0、0.0、(0+0i) 等

golang为了规避Java的空指针引用而引入了默认零值的设计。这导致了在设计程序的时候,遇到零值要小心处理。 比如上述这个结构,变量声明 var v AllBasicTypes 之后访问 v.B会得到falsev.U8 会得到 0

那么在实际的应用场景,就无法判断B到底是false,还是未赋值。解决的办法是另外加一个字典标记,或者使用 *bool 布尔指针这种奇怪的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
    "fmt"
    "time"
    "google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
    // 错误示例:使用 time.Time 的零值
    var zeroTime time.Time  // 或者 time.Time{}
    protoTs := timestamppb.New(zeroTime)
    
    // 序列化时就会报错
    data, err := proto.Marshal(protoTs)
    fmt.Println(err)  // 报错:proto: Google.Protobuf.Timestamp.Seconds out of range -62135596800
}

而时间的零值导致的proto Timestamp溢出是隐形的错误。 我们可能在底层的模型代码里面使用 time.Time 定义了某个字段,但在顶层API获取的时候发现反序列化失败。

弱网问题

在弱网环境,golang几乎是一种非常难用的编程语言。内置的语言包非常薄弱,经常需要go get,而国内 go get 则必定绕不过网络代理。

Go 模块:菱形依赖下对公共依赖 C 的多个版本要求

即便网络问题解决了,第三方的菱形依赖你也解决了。但在弱网情况下,golang还是很难用。一份源代码,下载到本地,开发+编译绕不过 vscode + golang插件 + go mod tidy。 而且golangvscode插件不是开箱即用的,安装之后还需要再安装 dlv 等工具。

兼容性问题

兼容性问题分两部分:操作系统兼容语言向前兼容

golang的不向前兼容

很多人不知道,其实golang在这么多年迭代的过程中出现过不向前兼容的变更:

Go 版本 变更类别 具体不兼容变更描述 控制方式 / 备注
Go 1 (2012) 语言 & 标准库重大重构 pre-Go 1 (r60 等) 到 Go 1 的巨大变更:包路径调整(e.g. encoding/asn1)、os.Error → error、time 包重构、map/delete 语法、rune 类型引入、map 遍历随机化等。 使用 go fix 工具辅助迁移。这是历史上最大的一次 breaking update。
Go 1.1 语言 & 平台 整数除常量零变为编译错误;64 位平台 int/uint 变为 64 位;部分 net/syscall 结构体/签名变更。 直接影响编译或运行行为。
Go 1.5 运行时 GOMAXPROCS 默认从 1 改为 CPU 核心数(调度行为变更)。 性能/并发相关,可能影响旧假设。
Go 1.21 运行时 & panic panic(nil)panic(untyped nil) 现在 panic *runtime.PanicNilError(之前无 panic)。 GODEBUG=panicnil=1 恢复旧行为(或 go 1.20 及更早)。
Go 1.21 包初始化 包初始化顺序算法精确定义(按 import path 排序),之前为未定义行为。 依赖隐式初始化顺序的代码可能受影响。
Go 1.22 语言(for 循环) for 循环变量每个迭代创建新变量(之前所有迭代复用同一变量)。闭包捕获行为改变。 go.mod 中的 go 1.22(或更高)启用。旧模块保留旧行为。这是常见迁移点。
Go 1.22 net/http.ServeMux ServeMux 支持方法前缀(如 POST /path)、通配符 {name} 等新模式;路径转义处理变更。 GODEBUG=httpmuxgo121=1 恢复 Go 1.21 行为。
Go 1.22 go/types 类型别名现在用 Alias 类型表示(之前等价于原类型)。 GODEBUG=gotypesalias=0(默认在 1.22);1.23 起默认 1,将在 1.27 移除。
Go 1.22 TLS & crypto 默认最小 TLS 版本提升至 1.2;移除部分 RSA key exchange 和 3DES 等 cipher(后续版本)。 多个 GODEBUG(如 tls10server=1tlsrsakex=1tls3des=1),部分将在 1.27 移除。
Go 1.23 time 包 time 包创建的 channel 变为 unbuffered(同步),影响 Timer.Stop 等正确使用。 GODEBUG=asynctimerchan=0 恢复旧异步行为(将在 1.27 移除)。
Go 1.23 net/http http.ServeContent 在错误响应时移除某些 header。 GODEBUG=httpservecontentkeepheaders=1 恢复旧行为。
Go 1.23 x509 & TLS 拒绝负序列号证书;Leaf 字段填充变更等。 多个 GODEBUG(如 x509negativeserial=0x509keypairleaf=0)。
Go 1.24 x509 x509 证书策略(Policies)字段使用变更。 GODEBUG=x509usepolicies=0 恢复旧行为。
Go 1.25 运行时 & nil 检查 某些 nil 指针 dereference(e.g. f, err := os.Open(); f.Name() 当 f==nil 时)现在严格立即 panic(之前某些情况延迟)。 严格遵守规范;无 GODEBUG 控制,需修复代码(先检查 err)。
Go 1.25+ 平台支持 移除对旧 OS 支持(如 macOS 11、32-bit windows/arm);Wasm 导出指令变更。 平台/移植相关,非 API 但影响构建。

在高版本golang中解析旧版本golang生成的证书,因为 x509部分的变更,可能出现解析失败的问题。

而语言不兼容的另一方面体现,是工具集的割裂。这在开发proto相关的时候就特别明显。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 核心基础
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 数据验证
go install github.com/envoyproxy/protoc-gen-validate@latest

# HTTP 网关
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest

# API 文档
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest

你安装了这么多的工具集。但当你切换到低版本的golang语言,就会发现 dlv 甚至都运行不了。 你需要找各种兼容当前版本的golang,最后在一堆工具集中迷失自我

实际情况并不允许你一直使用最新版本的golang,所以最后行为链路会变得非常奇怪:

window使用path路径切换到低版本golang –> 调试工具集不支持 –> 重新寻找支持当前版本的golang工具集ABCD –> A弄好了,弄B,B弄好了找C,然后一直循环往复。

最后你忘了其实你只是想要跑个程序,却一直把时间浪费在 go install xx version。

window不适合golang开发

而在window环境做golang开发是一个痛苦的事情。很多第三方工具,golang环境甚至无法编译成功,go install 就是一个笑话。 最后为了方便,golang开发大家都会尽可能地使用Linux/mac 系统。

选择编译缺失

Go 没有 C# / C 系那种预处理器级的条件编译:同一套语法里用 #if 把整段代码从未选中的构建里直接抹掉。Go 里更接近的做法是 build tags//go:build)配合不同文件,或 runtime.GOOS 等运行时分支,语义和手感都不一样。若你从 C# 过来,会明显感到这种「选择性编译」能力的缺失。

C# 里常见用法大致如下(仅示意):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1) 按内置/项目符号裁剪代码块(DEBUG 由 Debug 配置自动定义)
#if DEBUG
    System.Diagnostics.Debug.WriteLine("只在 Debug 构建中存在");
#endif

#if MY_FEATURE
    // 需在 csproj 的 <DefineConstants> 或编译参数里定义 MY_FEATURE
    DoExperimentalStuff();
#endif

// 2) Conditional:未定义符号时,调用点会被编译器剔除(方法本身仍可存在)
using System.Diagnostics;

class App
{
    [Conditional("VERBOSE")]
    static void VerboseLog(string msg) => Console.WriteLine(msg);

    static void Main()
    {
        VerboseLog("这条在未定义 VERBOSE 时不会产生调用指令");
    }
}

对比之下,Go 要在「不同构建」下换实现,通常靠文件级条件(//go:build linux)或链接时注入,而不是在同一函数体里用 #if 折叠半页代码。

如果不用这种标签去做选择编译,那除了使用环境变量切换之外,我暂时想不到别的解决方案。

后记

写到这里,我突然想问自己一个问题:AI时代分享技术还有意义吗?

回想刚入行的时候,网络同行前辈的建议是写个博客记录整理一下遇到的问题和解决方案。至今这个习惯已经断断续续坚持了10年。

现在是AI时代,人学习信息技术的速度已经完全跟不上AI。但批判性思维让我不太受AI的影响,我始终把AI当成辅助计算的手段。

毕竟最终的决策风险要我本人承担。这么多年的风风雨雨,告诉我只有把命运掌握在自己手中,才不会被外物牵着走。

所以我觉得,写技术博客本身没有意义。思考本身就是意义,归纳是一个过程,文章只是一个供后人怀念的结果。

In one sentence: golang is a high-performance language for network programming—nothing more, nothing less.

Zero-value traps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type AllBasicTypes struct {
	B    bool
	Str  string

	I8   int8
	I16  int16
	I32  int32
	I64  int64
	I    int

	U8   uint8
	U16  uint16
	U32  uint32
	U64  uint64
	U    uint
	Up   uintptr

	By   byte // uint8
	Ru   rune // int32

	F32  float32
	F64  float64

	C64  complex64
	C128 complex128
}

// var v AllBasicTypes — zero values: false, "", 0, 0.0, (0+0i), etc.

To avoid null-pointer-style issues like in Java, golang uses default zero values. That means you must be careful with zero values when you design programs. For the struct above, after var v AllBasicTypes, v.B is false and v.U8 is 0.

In real scenarios you cannot tell whether B is false or simply unset. The usual fixes are an extra map for “was it set?” or the odd choice of a *bool pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
    "fmt"
    "time"
    "google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
    // Bad example: zero value of time.Time
    var zeroTime time.Time  // or time.Time{}
    protoTs := timestamppb.New(zeroTime)
    
    // Serialization fails
    data, err := proto.Marshal(protoTs)
    fmt.Println(err)  // e.g. proto: Google.Protobuf.Timestamp.Seconds out of range -62135596800
}

A zero time.Time quietly blows up proto Timestamp handling. You might define a field as time.Time deep in the model, then fail deserialization at the top-level API.

Weak networks

On a poor network, golang is awkward to use: the standard library is thin, you constantly need go get, and in China go get almost always means fighting proxies.

Go modules: diamond dependency with multiple required versions of shared dependency C

Even if the network and third-party diamond dependencies are “solved,” golang is still painful under weak connectivity. You check out one repo and still cannot escape vscode + the golang extension + go mod tidy for dev and build. The vscode extension is not turnkey either—you install it, then still install dlv and more.

Compatibility

Compatibility splits into OS-level compatibility and forward language compatibility.

When golang is not forward-compatible

Many people do not realize golang has shipped backward-incompatible changes over the years:

Go version Category What broke Mitigation / notes
Go 1 (2012) Language & stdlib overhaul Huge jump from pre-Go 1 (r60, etc.): package paths (e.g. encoding/asn1), os.Errorerror, time redesign, map/delete, rune, randomized map iteration, … Use go fix. Largest breaking migration ever.
Go 1.1 Language & platforms Integer divide-by-zero on constants is an error; int/uint 64-bit on 64-bit; some net/syscall shapes/signatures. Direct compile/runtime impact.
Go 1.5 Runtime GOMAXPROCS default 1 → number of CPUs. Concurrency/perf assumptions may change.
Go 1.21 Runtime & panic panic(nil) / untyped nil panics *runtime.PanicNilError (previously no panic). GODEBUG=panicnil=1 or stay on go 1.20 or older.
Go 1.21 Package init Init order is now defined (import path order); previously undefined. Code relying on implicit init order may break.
Go 1.22 Language (for) Per-iteration loop variables (was one shared var). Closure capture changes. Enabled by go 1.22 (or newer) in go.mod; older modules keep old behavior. Common migration point.
Go 1.22 net/http.ServeMux Method prefixes (POST /path), {name} wildcards, escaping changes. GODEBUG=httpmuxgo121=1 restores Go 1.21 behavior.
Go 1.22 go/types Type aliases use Alias (previously same as underlying type). GODEBUG=gotypesalias=0 (default in 1.22); default 1 from 1.23; removed 1.27.
Go 1.22 TLS & crypto Min TLS 1.2; some RSA KEX / 3DES ciphers removed (later releases). Various GODEBUG flags (tls10server=1, …), some removed by 1.27.
Go 1.23 time Channels from time are unbuffered; affects correct Timer.Stop usage. GODEBUG=asynctimerchan=0 (removed 1.27).
Go 1.23 net/http http.ServeContent strips some headers on errors. GODEBUG=httpservecontentkeepheaders=1.
Go 1.23 x509 & TLS Reject negative serials; Leaf population changes, etc. GODEBUG such as x509negativeserial=0, x509keypairleaf=0.
Go 1.24 x509 Certificate Policies field handling. GODEBUG=x509usepolicies=0.
Go 1.25 Runtime & nil checks Some nil derefs (e.g. f, err := os.Open(); f.Name() when f==nil) panic immediately (was sometimes deferred). Spec-correct; no GODEBUG; fix code (check err first).
Go 1.25+ Platforms Drops old OS support (e.g. macOS 11, 32-bit windows/arm); Wasm export changes. Porting/build impact, not always API.

Parsing certs produced by older golang with a newer toolchain can fail because of x509 changes.

Another face of incompatibility is splintered tooling—especially obvious around proto.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Core
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Validation
go install github.com/envoyproxy/protoc-gen-validate@latest

# HTTP gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest

# API docs
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest

You install all of that—then drop to an older golang and even dlv may not run. You hunt for toolchains that match, and get lost in the matrix of plugins.

You cannot always stay on the newest golang, so workflows get weird:

Tweak path on window to an old golang → debugger stack unsupported → hunt for tools A B C D that match → fix A, then B, then C, loop forever.

You only wanted to run a program, but time burns on go install version pins.

window is a bad fit for golang

On window, many third-party tools fail to build; go install becomes a joke. So people doing golang gravitate to Linux / mac when they can.

No selective compilation

Go lacks preprocessor-style conditional compilation like C# / the C family: #if that strips whole regions from unselected builds. The closest Go offers is build tags (//go:build) across files, or runtime.GOOS branches—different semantics and ergonomics. Coming from C#, you feel the missing “selective compilation.”

Typical C# patterns (illustrative):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1) Trim code by symbol (DEBUG is defined in Debug builds)
#if DEBUG
    System.Diagnostics.Debug.WriteLine("Only in Debug builds");
#endif

#if MY_FEATURE
    // Define MY_FEATURE in csproj <DefineConstants> or compiler flags
    DoExperimentalStuff();
#endif

// 2) Conditional: if the symbol is undefined, call sites are stripped (method may remain)
using System.Diagnostics;

class App
{
    [Conditional("VERBOSE")]
    static void VerboseLog(string msg) => Console.WriteLine(msg);

    static void Main()
    {
        VerboseLog("No call instruction if VERBOSE is not defined");
    }
}

By contrast, Go swaps implementations per build mostly via file-level conditions (//go:build linux) or link tricks—not #if folding half a function.

Without those tags, aside from environment variables, I do not have another clean pattern.

Postscript

Writing this, I ask myself: does sharing tech still matter in the AI era?

When I started, seniors said: blog your problems and fixes. I have kept that habit, off and on, for ten years.

We cannot out-learn AI—but critical thinking keeps me from being swept along; I treat AI as a calculator.

Risk is still mine. Years of bumps taught me: hold your fate in your own hands, or the world drags you.

So the blog post itself is not the point. Thinking is the point—synthesis is the process; the article is just something for later readers to remember you by.

В двух словах: golang — это высокопроизводительный язык для сетевого программирования, не больше.

Ловушки нулевых значений

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type AllBasicTypes struct {
	B    bool
	Str  string

	I8   int8
	I16  int16
	I32  int32
	I64  int64
	I    int

	U8   uint8
	U16  uint16
	U32  uint32
	U64  uint64
	U    uint
	Up   uintptr

	By   byte // uint8
	Ru   rune // int32

	F32  float32
	F64  float64

	C64  complex64
	C128 complex128
}

// var v AllBasicTypes — нулевые значения полей: false, "", 0, 0.0, (0+0i) и т.д.

Чтобы не ловить «как в Java» проблемы с нулевыми указателями, в golang принята модель нулевых значений по умолчанию. Поэтому при проектировании с нулевыми значениями нужно быть осторожным. Для структуры выше после var v AllBasicTypes поле v.B даёт false, а v.U80.

В реальных сценариях непонятно: B это именно false или поле не задано. Обычно добавляют отдельную карту «установлено ли» или используют странный тип *bool.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
    "fmt"
    "time"
    "google.golang.org/protobuf/types/known/timestamppb"
)

func main() {
    // Плохой пример: нулевое значение time.Time
    var zeroTime time.Time  // или time.Time{}
    protoTs := timestamppb.New(zeroTime)
    
    // Сериализация падает
    data, err := proto.Marshal(protoTs)
    fmt.Println(err)  // например: proto: Google.Protobuf.Timestamp.Seconds out of range -62135596800
}

Ошибка из-за нулевого времени и proto Timestamp почти незаметна. Внизу стека вы могли объявить поле как time.Time, а на верхнем уровне API десериализация уже не проходит.

Слабая сеть

При плохой сети golang неудобен: стандартная библиотека скромная, постоянно нужен go get, а в Китае без прокси go get почти нереалистичен.

Go-модули: ромбовидная зависимость и несколько версий общей зависимости C

Даже если сеть и ромбовидные зависимости третьих сторон «решены», при слабом канале golang всё равно мучителен. Исходники скачал — а разработка и сборка всё равно упираются в vscode, расширение golang, go mod tidy. Расширение для vscode не «из коробки»: поставил — и всё равно ставишь dlv и прочее.

Совместимость

Совместимость делится на совместимость ОС и совместимость языка вперёд по версиям.

Когда golang не совместим вперёд

Мало кто знает, что за годы в golang были несовместимые вперёд изменения:

Версия Go Категория Что сломалось Обход / примечание
Go 1 (2012) Язык и стандартная библиотека Огромный разрыв с pre-Go 1 (r60 и т.д.): пути пакетов (напр. encoding/asn1), os.Errorerror, переработка time, map/delete, rune, случайный порядок обхода map и т.д. go fix. Крупнейшая ломающая миграция.
Go 1.1 Язык и платформы Деление константы на ноль — ошибка; на 64-bit int/uint 64-битные; часть net/syscall. Прямой эффект на сборку и выполнение.
Go 1.5 Рантайм GOMAXPROCS по умолчанию 1 → число CPU. Меняются допущения про параллелизм.
Go 1.21 Рантайм и panic panic(nil) / нетипизированный nil паникует *runtime.PanicNilError (раньше не паниковал). GODEBUG=panicnil=1 или go 1.20 и ниже.
Go 1.21 Инициализация пакетов Порядок инициализации задан (по import path); раньше — не определён. Код, завязанный на неявный порядок, может сломаться.
Go 1.22 Язык (for) Переменная цикла на каждую итерацию (раньше одна на все). Меняется захват в замыканиях. Включается go 1.22+ в go.mod; частая точка миграции.
Go 1.22 net/http.ServeMux Префиксы методов (POST /path), шаблоны {name}, экранирование. GODEBUG=httpmuxgo121=1 — поведение Go 1.21.
Go 1.22 go/types Псевдонимы типов как Alias (раньше совпадали с основным типом). GODEBUG=gotypesalias=0 (по умолчанию в 1.22); с 1.23 по умолчанию 1; уберут в 1.27.
Go 1.22 TLS и crypto Минимум TLS 1.2; выкидывание RSA KEX, 3DES и т.д. Несколько флагов GODEBUG, часть снимут в 1.27.
Go 1.23 time Каналы из time — небуферизованные; влияет на корректный Timer.Stop. GODEBUG=asynctimerchan=0 (уберут в 1.27).
Go 1.23 net/http http.ServeContent убирает часть заголовков при ошибках. GODEBUG=httpservecontentkeepheaders=1.
Go 1.23 x509 и TLS Отрицательные серийные номера; изменения с полем Leaf и т.д. x509negativeserial=0 и др.
Go 1.24 x509 Политики сертификатов (Policies). GODEBUG=x509usepolicies=0.
Go 1.25 Рантайм и nil Часть разыменований nil (напр. f, err := os.Open(); f.Name() при f==nil) паникует сразу (раньше иногда откладывалось). Без GODEBUG; чинить код (сначала err).
Go 1.25+ Платформы Снятие поддержки старых ОС (напр. macOS 11, 32-bit windows/arm); изменения Wasm. Влияет на сборку/порты.

Разбор сертификатов, сгенерированных старым golang, новым компилятором может падать из-за изменений в x509.

Другая сторона — раскол инструментов; с proto это особенно заметно.

1
2
3
4
5
6
7
8
9
10
11
12
13
# База
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Валидация
go install github.com/envoyproxy/protoc-gen-validate@latest

# HTTP-шлюз
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest

# Документация API
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest

Столько всего поставил — а на старом golang даже dlv не стартует. Ищешь совместимые версии и теряешься в наборе плагинов.

Не всегда можно сидеть на последнем golang, поэтому цепочка действий становится странной:

Меняешь path на window на старый golang → стек отладки не подходит → ищешь инструменты A B C D под версию → починил A, потом B, потом C, по кругу.

Ты хотел просто запустить программу, а время уходит на go install с версиями.

window плохо подходит под golang

На window много сторонних утилит не собирается; go install превращается в анекдот. Поэтому для golang чаще уходят на Linux / mac.

Нет условной компиляции в стиле препроцессора

У Go нет препроцессорной условной компиляции как у C# / C: #if, вырезающего целые куски из несобранных конфигураций. Ближе всего — build tags (//go:build) по разным файлам или ветки runtime.GOOS; смысл и эргономика другие. С фона C# явно чувствуешь отсутствие «выборочной компиляции».

Типичные приёмы в C# (схематично):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1) Обрезка кода по символу (DEBUG задаётся в Debug)
#if DEBUG
    System.Diagnostics.Debug.WriteLine("Только в Debug-сборке");
#endif

#if MY_FEATURE
    // MY_FEATURE — в csproj <DefineConstants> или флагах компилятора
    DoExperimentalStuff();
#endif

// 2) Conditional: если символ не задан, вызовы вырезаются (метод может остаться)
using System.Diagnostics;

class App
{
    [Conditional("VERBOSE")]
    static void VerboseLog(string msg) => Console.WriteLine(msg);

    static void Main()
    {
        VerboseLog("Без VERBOSE вызов не попадёт в IL");
    }
}

Для Go смена реализации между сборками — обычно условия на уровне файлов (//go:build linux) или трюки линковки, а не #if на полфункции.

Без таких тегов, кроме переменных окружения, другого аккуратного варианта я не вижу.

Постскриптум

Пишу это и спрашиваю себя: есть ли смысл делиться технологиями в эпоху ИИ?

Когда я начинал, советовали вести блог с проблемами и решениями. Так я делаю с перерывами уже десять лет.

Скорость обучения людей не догоняет ИИ — но критическое мышление не даёт увлечься потоком; ИИ для меня вспомогательный «калькулятор».

Риск всё равно на мне. Годы показали: судьбу держишь в своих руках — или тебя волокут обстоятельства.

Значит сам пост не цель. Смысл — в размышлении; систематизация — процесс; статья — лишь след для тех, кто потом вспомнит.



💬 讨论 / Discussion

对这篇文章有想法?欢迎在 GitHub 上发起讨论。
Have thoughts on this post? Start a discussion on GitHub.

在 GitHub 参与讨论 / Discuss on GitHub