Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions posts/C# 的 IDisposable 接口.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void foo()
// 使用 _event ...
event.Dispose();
}
```text
```

貌似没什么困难嘛,我们把每个对象的 Dispose 方法都调用一下,不就得了?然而问题远远不是这么简单。很多时候你根本搞不清楚什么时候该释放一个对象,因为它存在于一个复杂,动态变化的数据结构里面。除非你使用引用计数,否则你没有办法确定调用 Dispose 的时机。如果你过早调用了 Dispose 方法,而其实还有人在用它,就会出现严重的错误。

Expand All @@ -45,7 +45,7 @@ void main()

printf("%d, %d, %d\n", *a, *b, *c);
}
```text
```

你知道这个程序最后是什么结果吗?自己运行一下看看吧。所以对于复杂的数据结构,比如图节点,你就只好给对象加上引用计数。相信我,使用引用计数很痛苦。或者如果你的内存够用,也不需要分配释放很多中间结果,那你就干脆把这些对象都放进一个“池子”,到算法结束以后再一并释放它们……

Expand Down Expand Up @@ -88,7 +88,7 @@ void foo()
// ...
x = null;
}
```text
```

`x = null` 是毫无意义的。写出这样的代码,说明他们不明白 GC 是如何工作的,以为把引用设为 null 就可以释放内存,以为不把引用设为 null ,内存就不会被回收!再进一步,如果你仔细看 HashAlgorithm 的源代码,就会发现 HashValue 这个成员数组其实没有必要存在,因为它保存的只是上一次调用 ComputeHash() 的结果而已。这种保存结果的事情,本来应该交给使用者去做,而不是包揽到自己身上。这个数组的存在,还导致你没法重用同一个 HashAlgorithm 对象,因为有共享的成员 HashValue ,所以不再是 thread safe 的。

Expand Down Expand Up @@ -116,7 +116,7 @@ void UseFoo()
foo.Dispose(); // 没必要
foo = null; // 没必要
}
```text
```

这里的 `foo.Dispose()` 是完全没必要的。你甚至没必要写 `foo = null`,因为 foo 是一个局部变量,它一般很快就会离开作用域的。当函数执行完毕,或者编译器推断 foo 不会再次被使用的时候, GC 会回收整个 Foo 对象,包括里面的巨大数组。

Expand Down Expand Up @@ -158,7 +158,7 @@ protected override void Dispose(bool disposing)
{
Dispose(false);
}
```text
```

当 SafeHandle 被 GC 回收的时候, GC 会自动自动调用这个析构函数,进而调用 Dispose 。也就是说,你其实并不需要手动调用这些对象(例如 ManualResetEvent, Semaphore 之类)的 Dispose 方法,因为 GC 会调用它们。这些对象占用资源不多,系统里也不会有很多这种对象,所以 GC 完全应该有能力释放它们占用的系统资源。

Expand Down
2 changes: 1 addition & 1 deletion posts/Kotlin 和 Checked Exception.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ catch (Exception)
{
...
}
```text
```

注意到了吗,这也就是你写 Java 代码时,能写出的最糟糕的异常处理代码!因为不知道 foo 函数里面会有什么异常出现,所以你的 catch 语句里面也不知道该做什么。大部分人只能在里面放一条 log ,记录异常的发生。这是一种非常糟糕的写法,不但繁复,而且可能掩盖运行时错误。有时候你发现有些语句莫名其妙没有执行,折腾好久才发现是因为某个地方抛出了异常,所以跳到了这种 catch 的地方,然后被忽略了。如果你忘了写 catch (Exception),那么你的代码可能运行了一段时间之后当掉,因为忽然出现一个测试时没出现过的异常……

Expand Down
20 changes: 10 additions & 10 deletions posts/对 Rust 语言的分析.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ var id = ...;
var slot = ...;
var user = ...;
var passwd = ...;
```text
```

我需要把鼠标移到变量上面,让 Visual Studio 显示出它推导出来的类型,可是鼠标移开之后,我可能又忘了它是什么。有时候发现看同一片代码,都需要反复的做这件事,鼠标移来移去的。而且要是没有 Visual Studio ,用其它编辑器,或者在 github 上看代码或者 code review 的时候,你就得不到这种信息了。很多 C# 程序员为了避免这个问题,开始用很长的变量名,把类型的名字加在变量名字里面去,这样一来反而更复杂了,却没有想到直接把类型写出来。所以这种形式的类型推导,看似先进或者方便,其实还不如直接在声明处写下变量的类型,就像 Java 那样。

Expand All @@ -93,7 +93,7 @@ Rust 的文档说它是一种“[大部分基于表达式](https://doc.rust-lang
```rust
let mut y = 5;
let x = (y = 6); // x has the value `()`, not `6`
```text
```

奇怪的是,这里变量 `x` 会得到一个值,空的 tuple ,`()`。这种思路不大对,它是从像 OCaml 那样的语言照搬过来的,而 OCaml 本身就有问题。在 OCaml 里面,如果你使用 `print_string`,那你会得到如下的结果:

Expand All @@ -102,7 +102,7 @@ print_string "hello world!\n";;

hello world!
- : unit = ()
```text
```

这里,`print_string` 是一个“动作”,它对应过程式语言里面的“statement”。就像 C 语言的 `printf`。动作通常只产生“副作用”,而不返回值。在 OCaml 里面,为了“理论的优雅”,动作也会返回一个值,这个值叫做 `()`。其实 `()` 相当于 C 语言的 void 。 C 语言里面有 void 类型,然而它却不允许你声明一个 void 类型的变量。比如你写

Expand All @@ -111,7 +111,7 @@ int main()
{
void x;
}
```text
```

程序是没法编译通过的(试一试?)。让人惊讶的是,古老的 C 的做法其实是正确的,这里有比较深入的原因。如果你把一个类型看成是一个集合(比如 int 是机器整数的集合),那么 void 所表示的集合是个空集,它里面是不含有任何元素的。声明一个 void 类型的变量是没有任何意义的,因为它不可能有一个值。如果一个函数返回 void ,你是没法把它赋值给一个变量的。

Expand All @@ -129,15 +129,15 @@ Rust 的设计者似乎很推崇“面向表达式”的语言,所以在 Rust
fn add_one(x: i32) -> i32 {
x + 1
}
```text
```

返回函数里的最后一个表达式,而不需要写 return 语句,这是函数式语言共有的特征。然而其实我觉得直接写 return 其实是更好的作法,像这个样子:

```rust
fn foo(x: i32) -> i32 {
return x + 1;
}
```text
```

编程有一个容易引起问题的作法,叫做“不够明确”,总想让编译器自动去处理一些问题,在这里也是一样的问题。如果你隐性的返回函数里最后一个表达式,那么每一次看见这个函数,你都必须去搞清楚最后一个表达式是什么,这并不是每次都那么明显的。比如下面这段代码:

Expand All @@ -160,7 +160,7 @@ fn add_one(x: i32) -> i32 {
x / 2
}
}
```text
```

由于 if 语句里面有嵌套,每个分支又有好些代码,而且 if 语句又是最后一个语句,所以这个嵌套 if 的三个出口的最后一个表达式都是返回值。如果你写了“return”,那么你可以直接看有几个“return”,或者拿编辑器加亮一下,就知道这个函数有几个出口。然而现在没有了“return”这个关键字,你就必须把最后那个 if 语句自己看清楚了,找到每一个分支的“最后表达式”。很多时候这不是那么明显,你总需要找一下,而且这件事在读代码的时候总是反复做。

Expand All @@ -180,15 +180,15 @@ fn main() {
m[0] = 10; // 出错
m = [4, 5, 6]; // 也出错
}
```text
```

```rust
fn main() {
let mut m = [1, 2, 3]; // 指针和元素都可变
m[0] = 10; // 不出错
m = [4, 5, 6]; // 也不出错
}
```text
```

### 内存管理

Expand All @@ -203,7 +203,7 @@ Rust 那些炫酷的 move semantics, borrowing, lifetime 之类的概念加在
```rust
fn foo<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
}
```text
```

上一次我看 Rust 文档的时候,没发现有 lifetime 这概念。文档对此的介绍非常粗略,仔细看了也不知道他们在说些什么,更不要说相信这办法真的管用了。对不起,我根本不想去理解这些尖括号里的 `'a` 和 `'b` 是什么,除非你先向我证明这些东西真的能解决内存管理的问题。实际上这个 lifetime 我感觉像是跨过程静态分析时产生的一些标记,要知道静态分析是无法解决内存管理的问题的,我猜想这种 lifetime 在有递归函数的情况下就会遇到麻烦。

Expand Down
21 changes: 16 additions & 5 deletions scripts/crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,25 @@ def format_content(content: str) -> str:
)

# --- code-lang: add `text` to bare opening code fences ---
# Matches fenced code blocks: up to 3 leading whitespace chars, 3+ ticks,
# then an info string of any non-backtick chars (so ```c#, ```c++ work).
# ```code``` (inline) won't match because info ends before a closing run.
FENCE_RE = re.compile(r"^(\s{0,3})(`{3,})([^`]*)$")
in_code_block = False
lines = content.split("\n")
for i, line in enumerate(lines):
stripped = line.strip()
if re.match(r"^```\w*$", stripped):
if not in_code_block and stripped == "```":
lines[i] = "```text"
in_code_block = not in_code_block
m = FENCE_RE.match(line)
if not m:
continue
fence, info = m.group(2), m.group(3).strip()
if not in_code_block:
# Opening fence: add `text` if language is missing
if not info:
lines[i] = f"{fence}text"
in_code_block = True
else:
# Closing fence: leave as-is (CommonMark forbids info on close)
in_code_block = False
content = "\n".join(lines)

# --- cjk-spacing: Pangu spacing (CJK <-> half-width) ---
Expand Down
100 changes: 100 additions & 0 deletions scripts/fix_code_fences.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
One-shot fix for malformed code fences produced by a bug in crawler.py's
format_content() (now fixed).

The old fence regex `^```\w*$` failed to recognize opening fences whose
language tag contains non-word characters (e.g. ```c#, ```c++, ```f#). When
such an opening fence went unrecognized, the state machine did not flip, so
the *next* bare ``` (a legitimate closing fence) was misclassified as an
opening fence and rewritten to ```text. Per CommonMark, a closing fence
cannot carry an info string, so that ```text actually *opened* a new code
block, swallowing all following body text.

This script walks every post, tracks fence state with a regex that accepts
any language tag, and reverts any closing fence that incorrectly carries an
info string (```text → ```). Idempotent and minimal: it only touches lines
inside a code block whose closing fence has a non-empty info string.

Usage:
python3 scripts/fix_code_fences.py [--dry-run]
"""

import argparse
import re
import sys
from pathlib import Path

POSTS_DIR = Path(__file__).resolve().parent.parent / "posts"

# Matches fenced code blocks: up to 3 leading whitespace chars, 3+ ticks,
# then an info string of any non-backtick chars (so ```c#, ```c++ work).
# ```code``` (inline) won't match because info ends before a closing run.
FENCE_RE = re.compile(r"^(\s{0,3})(`{3,})([^`]*)$")


def fix_body(body: str) -> tuple[str, list[str]]:
"""Fix malformed closing fences. Returns (new_body, list_of_changes)."""
lines = body.split("\n")
changes = []
in_block = False
for i, line in enumerate(lines):
m = FENCE_RE.match(line)
if not m:
continue
fence, info = m.group(2), m.group(3).strip()
if not in_block:
in_block = True
else:
# Closing fence: CommonMark forbids info strings here. If present,
# it is the crawler bug — strip it.
if info:
changes.append(f" line {i + 1}: {line!r} -> {fence!r}")
lines[i] = fence
in_block = False
return "\n".join(lines), changes


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--dry-run", action="store_true", help="report without writing")
args = parser.parse_args()

if not POSTS_DIR.exists():
print(f"Posts dir not found: {POSTS_DIR}", file=sys.stderr)
return 1

total_fixed = 0
files_fixed = 0
for md in sorted(POSTS_DIR.glob("*.md")):
text = md.read_text(encoding="utf-8")

# Split off frontmatter (anything before the first ``` block)
fm_end = 0
if text.startswith("---"):
end = text.find("\n---", 3)
if end != -1:
fm_end = end + 4 # include the closing ```\n... actually closing '---'
# content after '---\n'
frontmatter = text[:fm_end] if fm_end else ""
body = text[fm_end:]

new_body, changes = fix_body(body)
if not changes:
continue

files_fixed += 1
total_fixed += len(changes)
print(f"{md.name}:")
for c in changes:
print(c)
if not args.dry_run:
md.write_text(frontmatter + new_body, encoding="utf-8")

mode = "DRY RUN — " if args.dry_run else ""
print(f"\n{mode}{files_fixed} file(s), {total_fixed} fence(s) fixed")
return 0


if __name__ == "__main__":
sys.exit(main())