<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[Coin's Blog]]></title>
        <description><![CDATA[Coin's Blog]]></description>
        <link>https://old.liaoguoyin.com</link>
        <image>
            <url>https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/R42rwjWE7.png</url>
            <title>Coin&apos;s Blog</title>
            <link>https://old.liaoguoyin.com</link>
        </image>
        <generator>RSS for Node</generator>
        <lastBuildDate>Thu, 14 May 2026 09:12:59 GMT</lastBuildDate>
        <atom:link href="https://old.liaoguoyin.com/atom" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[用 Git 裸仓库在多台内网机器中同步代码]]></title>
            <description><![CDATA[<p>由于一些特殊的环境限制，个人需要在多台内网主机之间同步代码仓库。</p>
<p>从表面上看，这似乎只是一个“文件同步”问题。我最初的方案也很直观：通过 Rsync, Syncthing 等应用软件同步代码文件夹和 .git 文件夹，这样既能同步代码数据，又能保留提交历史。</p>
<p>不过细想，这种做法经不起推敲。在弱网、断网或延迟同步的场景下，这种<strong>文件层级的同步</strong>几乎注定会破坏 .git 数据库。</p>
<p>举个简单的例子：如果 A、B 两台主机各自提交了新的代码（即形成了“硬分叉”），那么在它们重新联网、自动触发同步时，双向的文件更新会直接覆盖 .git 内部的索引与对象文件。结果不仅是当前分支元数据混乱，更严重的是整个历史记录可能被污染，甚至两台机器的仓库都可能损坏。而且这还只是两台主机的情况——随着同步节点数量增加，这种“分布式文件同步”方式只会让 .git 乱成一锅粥。</p>
<h2>0. 问题分析</h2>
<p>要避免上面的问题，必须重新审视两个核心点：</p>
<ul>
<li>
<p>同步方向：原方案是“双向同步”，双方在网络恢复后会同时互相写入，无法保证优先级，从而破坏仓库一致性。</p>
</li>
<li>
<p>同步操作原子性：文件同步程序控制同步触发时机的不可靠，弱网或中断时可能发生“同步一半”的非原子状态。</p>
</li>
</ul>
<p>经过一番调研后，发现 <strong>Git 裸仓库（bare repository）同步</strong> 能完美解决这两个问题：</p>
<ul>
<li>
<p><strong>单向同步机制</strong>：<br>
Git 的 push/pull 天然是单向的。任何节点都可以主动推送或拉取，仓库的版本流向始终可控。</p>
</li>
<li>
<p><strong>原子性与一致性保证</strong>：<br>
Git 在传输时只处理对象级别的差异，并且会有文件粒度的哈希校验，push/pull 操作要么全部成功，要么全部失败，不会出现“同步到一半”的状态。</p>
</li>
<li>
<p><strong>离线友好</strong>：<br>
各主机在离线时可独立提交，网络恢复后通过 <code>git push</code> / <code>git pull</code> 合并更新，过程完全受版本控制系统管理。</p>
</li>
</ul>
<p>因此，最后确定 <strong>Git 裸仓库作为中转节点</strong> 的方案做数据仓库同步，以下是一些相关的理论和实操记录，存档防忘。</p>
<h2>1. 什么是「裸仓库」？</h2>
<p>在普通 Git 仓库中，结构大致如下：</p>
<pre><code class="language-markup">
myrepo/
├── .git/ ← Git 数据库
└── src/ ← 工作区（实际文件）

</code></pre>
<p>而裸仓库（bare repository）去掉了工作区，只保留 .git 目录的内容结构：</p>
<pre><code class="language-markup">
myrepo.git/
├── HEAD
├── config
├── objects/
└── refs/

</code></pre>
<p>它只保存版本历史，不保存任何可编辑文件。也就是说，<strong>不能直接编辑代码，只能被 push / pull。</strong> 这也是 GitHub、GitLab 等远程托管服务背后的核心模型。</p>
<h2>2. 为什么要用裸仓库？（典型应用场景）</h2>
<ul>
<li>
<p>在两台机器之间同步开发代码</p>
</li>
<li>
<p>自建局域网版「私有 GitHub」</p>
</li>
<li>
<p>自动镜像同步到 GitLab / Gitee / 备份服务器</p>
</li>
<li>
<p>国内服务器不方便拉 GitHub 代码的中转方案</p>
</li>
<li>
<p>建立 CI/CD 触发源（<code>post-receive</code> 钩子）</p>
</li>
</ul>
<h2>3. 动手实践：两台机器间同步</h2>
<p>假设：</p>
<ul>
<li>
<p>开发机：<code>A</code></p>
</li>
<li>
<p>同步服务器：<code>B</code></p>
</li>
</ul>
<h3>3.1 在 B 上创建裸仓库</h3>
<pre><code class="language-bash">mkdir -p /git-server/repo.git
cd /git-server/repo.git
git init --bare

</code></pre>
<p>执行后目录结构如下：</p>
<pre><code class="language-markup">repo.git/
├── HEAD
├── objects/
├── refs/
└── config

</code></pre>
<h3>3.2 在 A 上添加远程并推送</h3>
<pre><code class="language-bash">cd ~/workspace/myproject
git remote add b ssh://user@B_IP:/git-server/repo.git
git push b main

</code></pre>
<p>此时，B 的 <code>/git-server/repo.git</code> 完整保存了 A 推送的文件更新。</p>
<p>你可以在任意机器 <code>git clone ssh://user@B_IP:/git-server/repo.git</code> 拉取最新版本。</p>
<h2>4. 在服务器也能看到最新源码</h2>
<p>如果你希望在服务器 B 上也能直接查看代码，可基于裸仓库克隆一份工作区：</p>
<pre><code class="language-bash">git clone /git-server/repo.git /git-server/repo-work
cd /git-server/repo-work
git checkout main

</code></pre>
<p>更新方式：</p>
<pre><code class="language-bash">cd /git-server/repo-work
git pull

</code></pre>
<h2>5. 让服务器自动推送到 GitLab（镜像）</h2>
<p>如果你希望这台裸仓库还能同步更新到 GitLab，可以用 <code>post-receive</code> 钩子。</p>
<pre><code class="language-bash">cd /git-server/repo.git
git remote add --mirror=push gitlab git@gitlab.com:&lt;group&gt;/&lt;project&gt;.git

</code></pre>
<p>然后创建钩子脚本：</p>
<pre><code class="language-bash">cat &gt; hooks/post-receive &lt;&lt;'SH'
#!/bin/sh
set -e
export HOME=/home/git   # ← 替换为实际用户的 HOME 路径
export PATH=/usr/bin:/bin
git push --mirror gitlab
SH

chmod +x hooks/post-receive

</code></pre>
<p>每次有 push 到这台服务器时，它都会自动把变更推到 GitLab。相当于基于 SSH 协议，自建了一个「私有中转层」。</p>
<h2>6. 命令速查表</h2>
<table>
<thead>
<tr>
<th>操作</th>
<th>命令</th>
</tr>
</thead>
<tbody>
<tr>
<td>创建裸仓库</td>
<td><code>git init --bare</code></td>
</tr>
<tr>
<td>添加远程</td>
<td><code>git remote add b ssh://user@host:/path/repo.git</code></td>
</tr>
<tr>
<td>推送代码</td>
<td><code>git push b main</code></td>
</tr>
<tr>
<td>自动同步 GitLab</td>
<td><code>hooks/post-receive</code> 中添加 <code>git push --mirror gitlab</code></td>
</tr>
<tr>
<td>同步工作区</td>
<td><code>git clone</code> / <code>git pull</code></td>
</tr>
</tbody>
</table>
<p>总的来说，裸仓库是没有工作区的纯 Git 仓库。换一个角度来看，我们平时推来推去的 GitHub、GitLab，背后其实就是一堆「.git 文件夹」 而已。</p>
<p>一想到当年 Linus 为了维护 Linux 内核，顺手写了这个 Git 就觉得牛逼到不行。</p>
<p>Elegant，实在是 Elegant。</p>
]]></description>
            <link>https://old.liaoguoyin.com/sync-with-git-bare</link>
            <guid isPermaLink="false">0d105032-1ce9-4a67-958b-c8b27234ab4b</guid>
            <pubDate>Fri, 10 Oct 2025 16:00:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[俺的电子垃圾列表]]></title>
            <description><![CDATA[<h2>主力设备</h2>
<ul>
<li>
<p>Macbook Pro M3 Max，48GB+1TB</p>
<ul>
<li>天天摸的干活机器，一步到位战五年版</li>
</ul>
</li>
<li>
<p>iPhone 15 Pro，256GB</p>
<ul>
<li>天天摸的手机，布老板官翻换新下来的，下一个不能换太好的，不然天天看</li>
</ul>
</li>
<li>
<p>iPad Mini 6，Celluar，64GB</p>
<ul>
<li>车机补充和日常家庭投影主控。只有蜂窝版有 GPS，才能高精度定位</li>
</ul>
</li>
<li>
<p>Macmini M4，16GB+256GB</p>
<ul>
<li>
<p>全屋软路由网关（Surge）</p>
</li>
<li>
<p>Docker 运行载体（Orbstack）</p>
</li>
</ul>
</li>
<li>
<p>AirPods Pro 3</p>
<ul>
<li>天天挂耳朵上，上班下班听李志。最近几年买得最值的电子产品，降噪一开就是沉浸</li>
</ul>
</li>
<li>
<p>Apple Watch SE，44mm</p>
<ul>
<li>
<p>看时间</p>
</li>
<li>
<p>睡眠检测，每天早上起床第一件事就是看昨天晚上有多久深度睡眠，睡得香的时候会和H老师比划比划</p>
</li>
</ul>
</li>
<li>
<p>RedmiBook，16GB+512GB</p>
<ul>
<li>Windows 备用本。社长曾经干活的机器，回购后给了俺，目前只有联机打红警的时候会开机 🤣</li>
</ul>
</li>
<li>
<p>零刻 ME mini NAS，4TB * 2</p>
<ul>
<li>
<p>全闪 NAS，备份、跨设备同步</p>
</li>
<li>
<p>Docker 运行载体</p>
</li>
<li>
<p>影音中心，飞牛影视挂削效果不错，回拉 115 和百度云资源，缓存到本地方便H老师刷剧</p>
</li>
</ul>
</li>
<li>
<p>JMGO P5X</p>
<ul>
<li>投影，1080P 分辨率，走到哪、提到哪、随便投屏（可投天花板）</li>
</ul>
</li>
<li>
<p>理光 GR III HDF</p>
<ul>
<li>网红卡片机。其实网红不网红无所谓，愿意拿出来拍照的机器才最重要（iPhone 有话说）</li>
</ul>
</li>
</ul>
<h2>外设</h2>
<ul>
<li>
<p>Apple Studio Display</p>
<ul>
<li>天天看的干活机器。C 口一线通+还不错的扬声器，买的时候觉得贵后牙槽都咬碎了，买之后天天用，香</li>
</ul>
</li>
<li>
<p>Niz 86</p>
<ul>
<li>
<p>公司高强度长时间写代码用，手感软软的，喜欢</p>
</li>
<li>
<p>静电容键盘，自己换了一套键帽（TODO：补图</p>
</li>
</ul>
</li>
<li>
<p>Logi Anywhere3</p>
<ul>
<li>鼠标，公司用，买的时候觉得亏，现在已经离不开它的长滚动了</li>
</ul>
</li>
<li>
<p>Logi MX Keys Mini</p>
<ul>
<li>在家天天摸的键盘，通过蓝牙多设备切换。插了收发 USB，作为显示器下游，跟随显示器上游 C 口线走</li>
</ul>
</li>
<li>
<p>TrackPad V2</p>
<ul>
<li>在家天天摸的触控板，通过蓝牙多设备切换。插了数据线，作为显示器下游，跟随显示器上游 C 口线走</li>
</ul>
</li>
<li>
<p>3D-Printed Topcase</p>
<ul>
<li>让键盘和触控板一体的塑料壳。实现了 Macbook 一样的键盘触控板布局体验</li>
</ul>
</li>
<li>
<p>ZTE F50</p>
<ul>
<li>随身 Wifi，给 Macmini 提供 fallback 蜂窝数据网络</li>
</ul>
</li>
</ul>
<p>小件：</p>
<ul>
<li>
<p>Samtisan R2</p>
<ul>
<li>收来感受锤子系统 TNT 和系统软件小巧思的，稳定吃灰中</li>
</ul>
</li>
<li>
<p>Apple Magsafe Duo</p>
<ul>
<li>二合一无线充，7.5W巨慢充，聊胜于无的无线充</li>
</ul>
</li>
<li>
<p>UGREEN 3 in 1</p>
<ul>
<li>三合一无限充，床头放，睡前迷糊磁吸充</li>
</ul>
</li>
<li>
<p>Belkin 3 in 1</p>
<ul>
<li>三合一无线充，干活桌上放，耳机和手表随手一放就能充，买了之后再也不会没电了</li>
</ul>
</li>
</ul>
<h2>智能家居</h2>
<ul>
<li>
<p>Xiaomi IoT</p>
<ul>
<li>
<p>买了一堆人在传感器和开关控制器改造，实现人来灯开人走灯灭</p>
</li>
<li>
<p>扫地机器人</p>
</li>
<li>
<p>空调伴侣</p>
</li>
<li>
<p>窗帘伴侣</p>
</li>
</ul>
</li>
<li>
<p>HomePod</p>
<ul>
<li>早起闹钟，隔空投送扬声器。低音沉闷有力，稳定吃灰中</li>
</ul>
</li>
<li>
<p>Xiaomi BE6500 Pro</p>
<ul>
<li>桥接光猫拨号 + 无线 AP + 小米蓝牙中枢</li>
</ul>
</li>
<li>
<p>小爱同学 Pro</p>
<ul>
<li>语音控制 + 各种 IoT 设备中枢，实测比 Siri 聪明</li>
</ul>
</li>
</ul>
<h2>最近出掉的设备（不建议版）</h2>
<ul>
<li>
<p>~Apple TV 4K：家庭影院播放盒子/投屏用。大家都在吹，装了 Infuse 等大家都推荐的套件，目前也没觉得有多好用，稳定吃灰中~</p>
</li>
<li>
<p>~小米中枢 ：实现智能家居的自动化定制化，稳定吃灰中~</p>
</li>
<li>
<p>~iPhone 13 Mini，128GB：备用机，韩版。一个物理卡槽（单卡插了~ <a href="https://estk.me">ESTK</a>~） + 一个 eSIM 卡槽~</p>
</li>
<li>
<p>~绿联 DXP 480T Plus：风扇动不动就酷酷转，一点全闪的样子都没有~</p>
</li>
<li>
<p>~HomePod Mini x 2：配套左右声道立体，作为 ATV 配套的扬声器~</p>
</li>
<li>
<p>NUC8i5H：黑苹果、软路由用，自从有了 2500 块钱的 M4 Macmini，就再也没有什么黑苹果的必要了</p>
</li>
</ul>
<h2>Recap</h2>
<p>很长一段时间，我总是控制不住喜欢捡电子垃圾。上面已经是我精简卖了好几波剩下的。整理下来，必要设备真的不多，而我还总是习惯骗骗自己（没买没体验过怎么知道它不好呢？反正还能卖二手；咋了嘛？哥们就这点爱好）</p>
<p>其实买卖二手的差价其实都是小钱，更糟糕的其实是陷进去的时间成本。下单纠结、收货把玩、研究对比、挂二手、出二手，寄快递巴拉巴拉，啰嗦得不得了，一趟下来的时间浪费了不知道多少。早些时间我还会写一些 Bot 去监控一些数码论坛指定关键词的二手东西，看到垃圾第一时间进去捡，也算是资深垃圾佬。</p>
<p>好在一段时间折腾下来，逐渐分清哪些是自己需要的、哪些是想要的，至少算是意识到了这个问题，在买卖对比上花的时间越来越少，这还算不错。整理这些电子垃圾不光是分享，更多的是希望自己梳理清楚，提醒自己不要再被电子垃圾玩垃圾时间了。</p>
<p>捡垃圾的时候我也和好多人打过交道，其中有个印象深刻的大哥：“只买必要的东西，而且买满足自己需求的最好的。用得久一点，买了之后也不会惦念着了升级的事情”，现在越来越觉得，这句话的含金量越来越高。</p>
]]></description>
            <link>https://old.liaoguoyin.com/coin-device</link>
            <guid isPermaLink="false">a03ec506-8f1f-46ba-a126-47c41e90866c</guid>
            <pubDate>Sun, 31 Aug 2025 07:09:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Hack Claude Code 使用 Kimi K2 模型]]></title>
            <description><![CDATA[<p>昨天晚上（20250711）刷到开源的 Kimi K2 模型（非推理模型）。</p>
<p><a href="https://github.com/MoonshotAI/Kimi-K2">官方号称：上下文 128K，总参数 1T，成本超低。</a></p>
<p>意外地是看到支持 Anthropic 协议，印象里面还没有其他官方模型主动支持的（大家都是 OpenAI compatible），这不是一下就来了兴致。研究了一下怎么在 Claude Code 中用 Kimi K2。</p>
<p>直接说结论，在已安装过 ClaudeCode 的情况下，不用安装任何新的玩意就能用上，具体步骤如下：</p>
<ul>
<li>
<p><a href="https://platform.moonshot.cn/console/api-keys">Kimi 官网生成 API key</a></p>
</li>
<li>
<p>打开命令行导出两个环境变量即可（ANTHROPIC_BASE_URL 和 ANTHROPIC_AUTH_TOKEN），见 P1。</p>
</li>
</ul>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/CMPTU0VEC.png" alt=""></p>
<pre><code class="language-markup">export ANTHROPIC_AUTH_TOKEN=sk-qViCuNrhyQaDPrELy3aM3UVZXcQmIz5Oh4iNRtm8XXXXXXXX
export ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic
claude

</code></pre>
<h2>在 CC 中使用外部模型的方法</h2>
<p>常规在 ClaudeCode 中使用外部模型有两种方法：Proxy 代理，LLM 网关。</p>
<h3>Proxy 代理</h3>
<p>测了 claude-code-router（claude-bridge、anthropic-proxy 未测）。结果发现 Proxy 在上下文维持上有问题，初始化就会触发 TPM（每分钟 Token 处理量）限制，基本不可用，见 P2。</p>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/Jnc4vzfzR.png" alt=""></p>
<h3>LLM 网关（推荐）</h3>
<p><a href="https://docs.anthropic.com/en/docs/claude-code/llm-gateway">LLM Gateway 是 Claude Code 官方支持的企业级方案。</a></p>
<p>这个口子本来是开给企业级 Claude 模型用的，但是 Kimi K2 主动兼容支持了下，对 ClaueCode 客户端来说，它认为在与 Anthropic 服务器通信，实际上请求被重定向到了 Kimi 服务器，返回的响应也符合 Anthropic API 的格式要求，这样一来就能在 ClaueCode 中用 Kimi K2 模型啦。</p>
<h4>绕过 Claude Code 认证</h4>
<p>打开项目文件夹，终端启动 Claude Code</p>
<pre><code class="language-hljs">cd /path/to/your_project
claude

</code></pre>
<p>如果是首次启动 Claude Code，会弹出登录认证网页。</p>
<p>只需要编辑 <code>$HOME/.claude.json</code> 添加新字段 <code>&quot;hasCompletedOnboarding&quot;: true</code> 即可。</p>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/BoPZrYk8H.png" alt=""></p>
<p>至此就是全部配置完成啦。实际体验下来，用 ClaudeCode 过程中还有概率爆 TPM，<a href="https://platform.moonshot.cn/docs/pricing/limits">账号升级 T1 后 TPM 扩大到 128000 才基本可用</a>（至少充 50 CNY），如果单次会话爆 TPM 可用 /clear 或开新会话。</p>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/KE99OYUCc.png" alt=""></p>
<h4>VScode + Cline 使用 Kimi K2</h4>
<p>另外，也尝试了下 VScode + Cline 使用 Kimi K2，UI 驱动逻辑打断太多，个人感觉没 ClaudeCode 丝滑。也可作为一个参考，见 P3。</p>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/9YSoSlhHx.png" alt=""></p>
<h4>初体验</h4>
<p>最后用 CC + K2 实现了一个简单的小想法（P4，P5）。</p>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/XLhPa64r2.png" alt=""><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/d067lLfr2.png" alt=""></p>
<h2>总结</h2>
<p>个人感觉 ClaudeCode + Kimi K2 是国内免魔法、免第三方号商情况下 Vibe Coding 的最优解。</p>
<p>总的来说，K2 模型还是挺不错的，开源且 API 便宜又大碗，直接作为 LLM 应用 API 也是个十分不错的选择。</p>
]]></description>
            <link>https://old.liaoguoyin.com/hacking-claude-code-using-kimi-k2</link>
            <guid isPermaLink="false">e23bad7e-713c-4a6c-87f1-cab1fa5ac51f</guid>
            <pubDate>Sat, 12 Jul 2025 13:39:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[使用 Clang-format 统一团队 C++ 代码风格]]></title>
            <description><![CDATA[<p>使用 Clang-format 工具在团队中统一 C++ 代码风格，提高代码可读性和维护性。</p>
<p>在团队中，统一代码风格可以有效提高内部项目可维护性，避免低级编码错误。</p>
<p>为实现这一目标，三板斧如下：</p>
<ol>
<li>确定格式化规范</li>
<li>选择格式化工具，编写调试配置文件</li>
<li>将规范集成到开发的各个流程中（编码开发，代码管理，编译打包），并跟进反馈修正</li>
</ol>
<h2>1. 确定格式化规范</h2>
<p>代码格式化规范已经非常标准化。常规标准有 LLVM，Google，Webkit 等。</p>
<p>笔者组内已有 C++ 编码规范：</p>
<p><img src="https://cdn.liaoguoyin.com/images/clang-format-team-cpp-code-style_1.png" alt="组内 C++ 编码规范"></p>
<p>常见的格式化规范要求涉及如下部分：</p>
<ol>
<li>命名规范</li>
</ol>
<ul>
<li>类、结构体、枚举、联合体、类型定义、作用域名</li>
<li>函数名(包括全局函数、作用域函数、成员函数)</li>
<li>全局变量(包括全局和命名空间域下的变量，类静态变量)，局部变量，函数参数，类、结构体和联合体中的成员变量名</li>
<li>宏、全局常量、枚举值、goto标签</li>
</ul>
<ol start="2">
<li>格式规范</li>
</ol>
<ul>
<li>括号位置</li>
<li>空格位置及使用情况</li>
<li>行宽</li>
<li>缩进</li>
</ul>
<ol start="3">
<li>注释规范</li>
<li>语言级高级特性使用建议</li>
</ol>
<h2>2. 选择格式化工具</h2>
<p>确定上述规范后，并不能指望团队成员能很好地应用规范。因此，需要选择格式化工具，通过介入开发的各个阶段，潜移默化地辅以开发者遵守规范。</p>
<p>CPP 常见的格式化工具有 <a href="https://github.com/cpplint/cpplint">cpplint</a>，<a href="https://clang.llvm.org/docs/ClangFormat.html">clang-format</a>，<a href="https://clang.llvm.org/extra/clang-tidy/index.html">clang-tidy</a> 等：</p>
<ul>
<li>cpplint 是格式化工具，主要围绕 Google 规范展开，现已不再维护公开仓库</li>
<li>clang-tidy 是静态代码分析工具，可以实现<a href="https://clang.llvm.org/extra/clang-tidy/checks/list.html">变量名风格纠正替换</a></li>
<li>clang-format 是格式化工具，调整空格换行符等格式规范</li>
</ul>
<p>经过初步调研后，发现 <a href="https://stackoverflow.com/questions/73788989/how-to-configure-naming-conventions-with-clang-format">clang-format 基本能满足所有需求（格式规范和注释规范），命名规范方面则可以结合 clang-tidy 可为补充</a>：</p>
<blockquote>
<p>Clang-format is all about local changes to the code in a way that is irrelevant to the compiler. Like changing whitespace. Renaming variables, on the other hand, is a completely different thing, since its impact is potentially very global (think about exported symbols consumed by other libraries, or just multiple files).</p>
</blockquote>
<p>下文是笔者集成 Clang-format 的一点实践经验。</p>
<h2>3. 编写 .clang-format</h2>
<p>引入 Clang-format 需要编写 .clang-format 配置文件，通过配置文件的形式调用的好处很多：无论是在命令行脚本中或是 IDE 中或是 CICD 流程，都能很方便地集成同一套规范。</p>
<p><a href="https://clang.llvm.org/docs/ClangFormatStyleOptions.html">Clang-format 支持的配置参数很多</a>，为提高编写配置文件效率，<a href="https://clang-format-configurator.site/">可以使用在线 .clang-format 配置预览网站</a>，修改后实时预览格式:
<img src="https://cdn.liaoguoyin.com/images/clang-format-team-cpp-code-style_2.png" alt="image-20250412223702866"></p>
<p>经过 GPT 辅助+文档查询+网站在线调试后，初版格式化配置文件如下：</p>
<pre><code class="language-YAML"># .clang-format 配置文件, 根据指定的编码规则生成

# 基础样式，基于 LLVM 并进行覆盖
BasedOnStyle: LLVM
SortIncludes: false

# 缩进设置
IndentWidth: 4                   # 规则4.3.1 使用4个空格缩进
UseTab: Never                    # 规则4.3.1 禁止使用制表符
TabWidth: 4
AccessModifierOffset: -4         # public:, protected:, private:，缩进与class对齐

# 大括号风格：Allman 风格（新行开始）
BreakBeforeBraces: Custom         # 规则4.4.1 Allman 风格的大括号
BreakBeforeBinaryOperators: None
BraceWrapping:
  AfterClass: true               # 类定义后大括号另起一行
  AfterControlStatement: true    # if, else, for, while 等控制语句后大括号另起一行
  AfterEnum: true                # 枚举定义后大括号另起一行
  AfterFunction: true            # 函数定义后大括号另起一行
  AfterNamespace: false          # 将命名空间的左大括号放在行末
  AfterExternBlock: false        # 将 extern &quot;C&quot; 的左大括号放在行末
  BeforeCatch: true              # catch 前大括号另起一行
  BeforeElse: true               # else 前大括号另起一行
  IndentBraces: false            # 大括号不额外缩进

# 控制语句设置
AlwaysBreakAfterReturnType: None              # 确保返回类型和函数名不在返回类型之后强制换行，保持在同一行
AlwaysBreakAfterDefinitionReturnType: None    # 专门针对函数定义，确保返回类型和函数名不在返回类型之后强制换行
AllowShortIfStatementsOnASingleLine: false    # 规则4.7.2 禁止单行 if 语句
AllowShortLoopsOnASingleLine: false           # 禁止单行 for/while 循环

# 函数声明和调用格式化
AllowAllParametersOfDeclarationOnNextLine: false     # 规则4.5.1 函数声明参数不全部放到下一行
BinPackParameters: false                             # 禁止在函数声明和定义中将多个参数打包在一行，确保每个参数在必要时单独换行
BinPackArguments: false                              # 禁止在函数调用中将多个参数打包在一行，确保在行宽不足时参数能够合理换行并对齐

# 参数对齐
AlignAfterOpenBracket: Align                        # 当参数换行时，后续行的参数与第一个参数对齐
ContinuationIndentWidth: 4                          # 规则4.3.1 连续行缩进4个空格，设置续行的缩进为4个空格，确保参数列表换行后的对齐缩进

# 控制括号前的空格
SpaceBeforeParens: ControlStatements                # 控制语句前加空格
SpaceAfterCStyleCast: false                         # C 风格强制转换后不加空格

# Switch 语句格式化
IndentCaseLabels: true                              # 规则4.9.1 case/default 缩进一层

# 指针对齐
PointerAlignment: Right                              # 指针靠左对齐，如 int* ptr;

# 等号操作符对齐
AlignOperands: true
AlignConsecutiveAssignments: true

# 预处理指令格式化
IndentPPDirectives: None                            # 规则4.14.1 预处理指令不缩进

# 最大空行数
MaxEmptyLinesToKeep: 2

# 禁止单行函数定义
AllowShortFunctionsOnASingleLine: None

# 命名约定（无法通过 Clang-Format 强制执行，需要使用 Clang-Tidy 或其他工具）
# 规则2.1.1 标识符命名使用驼峰风格

# 类成员访问控制格式化
# 规则4.16.1 公共、受保护、私有部分排列顺序，并与 class 对齐
# 注意：Clang-Format 并不直接支持访问控制排序，需要手动排列或使用其他工具

# 宏定义格式化（如果需要）
# 根据需要进行自定义，例如对齐等

# 规则4.2.1 行宽设置（按需调整）
ColumnLimit: 200
</code></pre>
<p>在确定了规范和工具后，可以在命令行中快速格式化单个 cpp 代码：
<code>clang-format -style=file -i src/path/to/*.cpp</code></p>
<blockquote>
<p>clang-format -style=file 应用配置文件时会遵循就近原则，在执行目录最近的父级目录中找寻 .clang-format 配置。找不到则使用默认的规则格式化兜底
<a href="https://clang.llvm.org/docs/ClangFormatStyleOptions.html">When using -style=file, clang-format for each input file will try to find the .clang-format file located in the closest parent directory of the input file. When the standard input is used, the search is started from the current directory.</a></p>
</blockquote>
<p>批量格式化也十分简单。将多个 cpp 文件传入给 clang-format 即可，这个过程需要借助 Shell 中的 <strong>通配符</strong> 或 <strong>find 指令+管道</strong>，以脚本的形式运行：</p>
<ul>
<li><code>find . -name '*.cpp' -o -name '*.h' | xargs clang-format -i</code></li>
<li><code>clang-format -i **/*.{cpp,h,hpp}</code></li>
</ul>
<h2>4. 集成策略</h2>
<p>有了规则和工具，接下来考虑的是如何把规范应用到小组内部项目中，实现长期规范。</p>
<p>如下图，在开发的各个流程中（编码开发，代码管理，编译打包），都可以集成格式化。</p>
<ul>
<li>Coding（vscode，qt creator format on saving）</li>
<li>Before git push（Git Hook： ~/.git/hooks）</li>
<li>After git push（CI Pipeline）</li>
</ul>
<p><img src="https://cdn.liaoguoyin.com/images/clang-format-team-cpp-code-style_3.png" alt="代码格式化阶段"></p>
<p>具体在哪个阶段引入，可以从侵入性、可复用性、能否提供反馈纠正等方面进行考虑。</p>
<ul>
<li>Coding 阶段引入需要引导开发者为 IDE 配置额外的工具，前期有一定侵入性，但配置独立于项目，每次格式化会给予开发者正反馈学习；</li>
<li>本地 Git 阶段引入也有侵入性，需要编写脚本配置项目粒度的 ~/.git/hooks，相比于第一阶段可复用性更差。每个项目都要配脚本、新增 git hooks 操作；</li>
<li>CICD pipeline 阶段对开发者是无感、或者说是被动的，可迁移性也更好，开发和格式化完全分离。这里重点描述 CI 阶段。CI 有两种策略：
<ol>
<li>一种是推送后执行 format，直接让 CI 将 format 之后的代码推送到仓库，这种策略对开发者感知很弱，几乎不会让开发者对代码规范引起重视，且对代码及具侵略性，缺乏人工 review 的过程可能导致危险；</li>
<li>另一种是在 CI 中执行 format 后，检查 git diff，如果 git 存在差异，则拒绝代码推送及 PR合并，让用户修改后再推送再合并。</li>
</ol>
</li>
</ul>
<p>好的工具应该是无感的，是辅助、加成而不是给开发者以限制。</p>
<p>因此，一个不错的引入原则是 <strong>多阶段结合，相互补充，逐步平滑引入</strong>：</p>
<p>在 Coding 阶段引入，并不断修正配置文件；等到配置成熟后，然后在 CI/CD 流水线中引入，增量检查变更代码，并按照策略2进行自自动化审查（没有格式化的代码无法被合并）。</p>
<p>至于第二个阶段，hooks 无法被同步到 Git 仓库，要求开发者需要在开发环境进行额外的配置，且可迁移性较差，暂时不予忽略，在后续有需要时再考虑引入。</p>
<h3>4.1 集成到开发阶段</h3>
<p>在开发时介入，这部分主要是在统一在开发者处安装 IDE 插件，实现保存文件时自动调用 Clang-format，这个后续会再写水一篇文章。</p>
<p>值得一提的是，<a href="https://askubuntu.com/questions/1409031/how-to-use-a-more-recent-clang-format-or-clang-tidy-version-on-ubuntu-18-04">Clang-format 程序版本应该保持一致性</a>。</p>
<p>笔者在实践时发现，组内有同事配置了 Clang-format 但依旧格式化不通过标准化结果。</p>
<p>最后发现是开发环境的锅，如 Ubuntu 22 和 Ubuntu 24 通过 apt 安装的 Clang-format 版本并不相同，不同的 Clang-format 版本之间加入了新的格式化规则，版本之间默认的配置参数并不完全兼容。</p>
<p><strong>因此需要开发团队安装同一个版本的 Clang-format，可以选择编译安装，也可以使用 Python pip 安装。</strong></p>
<h3>4.2 集成到 CI 流程</h3>
<p>在开发者推送代码到代码仓库后，可以在编译前进行格式化。这条链路的核心是：在 PR 合并代码阶段，利用 <a href="https://git-scm.com/docs/git-diffyesterday">git diff</a> 对比格式化前后的代码，接受或拒绝更新。</p>
<pre><code>#!/bin/bash

# 脚本名称: format_code.sh
# 功能：格式化指定目录下的 C/C++ 文件（.cpp, .hpp, .c, .h）
# 支持本地和 GitLab CI 环境调用

# 设置要格式化的文件类型
FILE_TYPES=(
    &quot;*.cpp&quot;
    &quot;*.hpp&quot;
    &quot;*.c&quot;
    &quot;*.h&quot;
)
# 设置要搜索的目录列表（支持换行）

SEARCH_DIRS=(
    &quot;src/&quot;
    # 可以根据需要添加更多目录
)

# 检查 clang-format 是否安装
if ! command -v clang-format &amp;&gt; /dev/null; then
    echo &quot;错误: clang-format 未安装。请先安装。&quot;
    exit 1
fi

# 遍历目录列表并格式化文件
for search_dir in &quot;${SEARCH_DIRS[@]}&quot;; do
    if [ ! -d &quot;$search_dir&quot; ]; then
        echo &quot;警告：目录 $search_dir 不存在，跳过。&quot;
        continue
    fi

    for file_type in &quot;${FILE_TYPES[@]}&quot;; do
        find &quot;$search_dir&quot; -type f -name &quot;$file_type&quot; | while read -r file; do
            echo &quot;格式化文件: $file&quot;
            clang-format -i &quot;$file&quot;
        done
    done
done

echo &quot;格式化完成。&quot;

</code></pre>
<p>CI 配置引入:</p>
<pre><code>
test-clang-format:
  stage: test
  script:
    - clang-format --version # clang-format &gt;= 19.1.7
    - bash scripts/format/format_code.sh
    # - shopt -s globstar # enable globstar, avaible from bash 4.0
    # - clang-format -i ./src/**/*.cpp ./src/**/*.h
    - git diff &gt; format.diff
    - echo &quot;格式化差异前十行如下，如果有结果则失败，需格式化之后再推送（您也可以下载 CI 过程中右侧的 Job artifacts 查看完整差异）&quot;
    - head format.diff
    - git diff --exit-code # exit if have diffs
  artifacts:
    when: always
    paths:
      - format.diff
  allow_failure: false

</code></pre>
<h2>5. 总结</h2>
<p>以上就是笔者在 cpp 项目中集成 Clang-format 的一点经验。</p>
<p>本文介绍了 Clang-format，.clang-format 配置文件编写的过程，以及将其集成到开发过程中哪个阶段的思考：</p>
<ul>
<li>目前个人的答案是将它集成到代码开头（开发阶段）和代码结束（代码 CI 编译前）</li>
<li>现在，借助 GPT 编写这类工具的配置文件是件简单得不能再简单的事情，如何更无感地把它集成到团队中，尽可能不影响开发体验和习惯才是更值得琢磨的</li>
</ul>
<p>此外，规则内容本身没有好坏之分。举例来说，细抠犹豫 CPP 指针符应该放在变量和类型的左边、右边还是中间，真没多少意义。</p>
<p>毕竟，保持一致性才是做这件事的核心。</p>
<h2>6. Ref</h2>
<ul>
<li><a href="https://bayareanotes.com/clang-tidy/">https://bayareanotes.com/clang-tidy/</a></li>
<li><a href="https://github.com/johnmcfarlane/unformat">https://github.com/johnmcfarlane/unformat</a></li>
<li><a href="https://coderfan.net/en/how-to-unify-code-stytle-in-c-or-c-plus-plus-html.html#google_vignette">https://coderfan.net/en/how-to-unify-code-stytle-in-c-or-c-plus-plus-html.html#google_vignette</a></li>
</ul>
]]></description>
            <link>https://old.liaoguoyin.com/clang-format-team-cpp-code-style</link>
            <guid isPermaLink="false">a6799955-b88b-48c3-a2cd-71431e79cecb</guid>
            <pubDate>Fri, 11 Apr 2025 18:47:48 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[配一台新 Mac 我都配些什么]]></title>
            <description><![CDATA[<p>最近装机频繁，抹掉系统之后装来装去就那几个 App，就那点设置。</p>
<p>索性记录下配置新 Mac 时的设置，顺便整理分享一些比较好用的 Mac App。</p>
<p>内容主要分为三大块：<a href="https://macos-defaults.com/">系统设置</a>、安装应用软件(Homebrew)、数据迁移。</p>
<h2>系统设置</h2>
<h3>键盘</h3>
<p>为了方便连续输入，调整按键重复速度：系统偏好设置 &gt; 键盘</p>
<ul>
<li>
<p>按键重复速度：最快</p>
</li>
<li>
<p>重复前延迟：短（或倒数第二格）</p>
</li>
</ul>
<h3>触控板</h3>
<p><a href="https://wild-flame.github.io/guides/docs/mac-os-x-setup-guide/preference_and_settings/readme">默认触控板需要按到底，且部分手势没开启，按需调整</a></p>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_1.png" alt="image-20250329124059851"></p>
<ul>
<li>
<p>修改点按力度，开启轻按触摸：系统设置 &gt; 触控板 &gt; 光标与点按 &gt; 点按「<strong>中</strong>」</p>
</li>
<li>
<p>快速单词查询，开启三指轻点：系统设置 &gt; 触控板 &gt; 光标与点按 &gt; 查询数据检测器「<strong>三指轻点</strong>」</p>
</li>
<li>
<p>实现鼠标右键，开启双指点按：系统设置 &gt; 触控板 &gt; 光标与点按 &gt; 辅助点按「<strong>双指点按</strong>」</p>
</li>
<li>
<p>避免误触发，关闭轻点：系统设置 &gt; 触控板 &gt; 光标与点按 &gt; 辅助点按关闭「<strong>轻点来点按</strong>」</p>
</li>
</ul>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_2.png" alt="image-20250329124642156"></p>
<ul>
<li><a href="https://sspai.com/post/39202">三指选中多行文本</a>。开启三指拖拽：系统设置 &gt; 辅助功能 &gt; 互动 &gt; 鼠标与触控板 &gt; 触控板选项「三指拖移」</li>
</ul>
<h3>台前调度设置</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_3.png" alt="1744471692361">非常糟糕的交互特性，容易误触，关闭台前调度中墙纸点按收放：设置 &gt; 桌面与拓展坞 &gt; 点按墙纸以显示桌面「仅在台前调度中」</p>
<h3>Finder 设置</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_4.png" alt="image-20250329123547842">打开 Finder，在屏幕右上角选择「偏好设置」（command + .）</p>
<ul>
<li>
<p>设置新窗口默认打开位置：Home 目录</p>
</li>
<li>
<p>自定义侧边栏选项</p>
</li>
<li>
<p>显示路径栏和状态栏</p>
</li>
</ul>
<h3>Terminal 设置</h3>
<p>Mac 自带的 Terminal 终端很好用，但缺点是比较简陋，文本既没高亮，信息又不完整。</p>
<p>可以通过修改 Shell 配置文件 <code>~/.zshrc</code> 来实现 <strong>文件夹高亮显示、完整路径显示。</strong></p>
<p>配置前后的差异如下：</p>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_5.png" alt="1"></p>
<pre><code class="language-bash">export CLICOLOR='Yes' # 是否输出颜色
export LSCOlORS='Exfxcxdxbxegedabagacad' # 定义 ls 命令输出的颜色和样式
export LC_ALL=en_US.UTF-8 # 设置所有区域设置为美国英语，字符编码为 UTF-8
export LANG=en_US.UTF-8 # 设置默认语言为美国英语，字符编码为 UTF-8
export PS1=&quot;%B%F{034}%m%f%b:%d %% &quot; # 设置命令提示符格式，包含主机名和当前目录

export LC_ALL=en_US.UTF-8 # 重复设置所有区域设置
export LANG=en_US.UTF-8 # 重复设置默认语言

</code></pre>
<h3>Time Machine</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_6.png" alt="Xnip2024-11-21_23-52-39">有 Mac，有 NAS，那么碎片化整机增量备份，Time Machine 自然少不了。</p>
<p>注：为避免 NAS 硬盘炒豆子噪音，可以降低备份频率到「每周一次」</p>
<h3>远程连接</h3>
<p>为了能随时方便地把本机当作服务器，通过 CLI 或者 VNC 形式进行连接，可以配置远程登录配置项实现 Ubuntu 下 openssh-server 的效果。</p>
<blockquote>
<ul>
<li>
<p>Mac Mini：<a href="https://www.youtube.com/watch?v=CITHNloGlnU">https://www.youtube.com/watch?v=CITHNloGlnU</a></p>
</li>
<li>
<p>文件共享：<a href="https://sspai.com/post/61388">https://sspai.com/post/61388</a></p>
</li>
</ul>
</blockquote>
<h3>禁用 .DS_Store</h3>
<p>用 Mac 压缩过文件的朋友应该都见过 zip 包中的 💩：<a href="https://zh.wikipedia.org/wiki/.DS_Store">.DS_Store</a>，<a href="https://www.betterzip.net/faq/mac-osx.html">__MACOSX</a></p>
<p>.DS_Store 文件（Desktop Service Store）是一种由苹果公司的 Mac OS X 操作系统所创造的隐藏文件，目的在于存贮目录的自定义属性，主要用于存放元数据，比如记录一些图标大小、查看方式等。</p>
<p>打开命令行，<a href="https://www.bilibili.com/video/BV1L4znYWECG/">禁用 .DS_Store 文件生成</a>：</p>
<pre><code class="language-bash">defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool TRUE

</code></pre>
<h2>开发工具</h2>
<h3>Surge</h3>
<p>配置好科学上网后，后续软件的安装难度呈指数级下降。</p>
<p>配置为软路由，DHCP 接管网络，可参考（TODO）：</p>
<ul>
<li>
<p><a href="https://dosbat.com/2024/10/08/Macmini+surge+asus%20mesh%E7%BB%84%E7%BD%91/index.html">https://dosbat.com/2024/10/08/Macmini+surge+asus%20mesh%E7%BB%84%E7%BD%91/index.html</a></p>
</li>
<li>
<p><a href="https://oftime.net/2021/07/27/net/">https://oftime.net/2021/07/27/net/</a></p>
</li>
<li>
<p><a href="https://qust.me/post/MacSurgeRouter/">https://qust.me/post/MacSurgeRouter/</a></p>
</li>
</ul>
<h3>Homebrew</h3>
<p><a href="https://brew.sh/">Homebrew</a> 是 Mac 下面的包管理工具（类似于 apt、yum），可以安装、卸载 Mac GUI/CLI 应用程序。</p>
<ul>
<li>安装 Command Line Tools</li>
</ul>
<pre><code class="language-bash">xcode-select --install

</code></pre>
<ul>
<li>安装 Homebrew</li>
</ul>
<pre><code class="language-bash">/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;

</code></pre>
<h3>NVM</h3>
<h2><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_7.png" alt="Xnip2024-11-21_23-38-56"></h2>
<p><a href="https://github.com/nvm-sh/nvm">NVM</a>（Node Version Manager）是 Node.js 版本管理工具，可以方便地安装卸载不同版本的 Node.js。</p>
<ul>
<li>安装 NVM</li>
</ul>
<pre><code class="language-bash">curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

</code></pre>
<ul>
<li>安装 Node.js 20，并启用</li>
</ul>
<pre><code class="language-bash">nvm install 20
nvm use 20

node --version # 验证 Node.js 版本
npm install -g yarn # 全局安装 Yarn 包管理器

</code></pre>
<h3>Claude Code</h3>
<ul>
<li>安装</li>
</ul>
<pre><code class="language-markup">npm install -g @anthropic-ai/claude-code
</code></pre>
<ul>
<li>备份恢复 ~/.claude 可以查看历史用量，对话历史记录，token 等</li>
</ul>
<h3>Codex CLI</h3>
<ul>
<li>安装</li>
</ul>
<pre><code class="language-markup">npm install -g @openai/codex
</code></pre>
<ul>
<li>
<p>备份 ~/.codex：</p>
<ul>
<li>仅备份 ~/.codex/auth.json 即可无登录切换，<a href="https://github.com/openai/codex/blob/main/docs/authentication.md">事实上官方也推荐这么做</a></li>
</ul>
</li>
</ul>
<h2>应用软件(Homebrew)</h2>
<p>通过 Homebrew 可以快速安装各种 GUI app：<code>brew install --cask 软件名标识符</code></p>
<p>个人使用 Homebrew 安装的软件如下。</p>
<pre><code class="language-markup"># 完整安装脚本见：https://gist.github.com/LiaoGuoYin/fe54e5e653ec3e2debbc02828189d651
# 开发工具包
echo &quot;Installing development tools...&quot;
brew install git
brew install vim
brew install curl
brew install wget
brew install tree
brew install jq

# 安装常用应用程序
echo &quot;Installing applications...&quot;
# 下文会详细介绍的 app
brew install --cask surge
brew install --cask 1password
brew install --cask baidunetdisk
brew install --cask downie
brew install --cask figma
brew install --cask handbrake
brew install --cask heynote
brew install --cask orbstack
brew install --cask raycast
brew install --cask rustdesk
brew install --cask spotify
brew install --cask typora
brew install --cask visual-studio-code
brew install --cask adobe-creative-cloud

# 其他常规软件
brew install --cask wechat
brew install --cask google-chrome
brew install --cask tencent-meeting
brew install --cask appcleaner
brew install --cask imageoptim
brew install --cask charles
brew install --cask postman
brew install --cask microsoft-office
brew install --cask telegram-desktop
brew install --cask proxyman
brew install --cask eudic
brew install --cask royal-tsx
brew install --cask iina
brew install --cask ogdesign-eagle
brew install --cask zerotier-one
brew install --cask tailscale
brew install --cask keka

</code></pre>
<p>下面捡几个详细介绍一下。</p>
<h3>OrbStack</h3>
<p>几年前用过 Docker for Desktop 不是很好用，用着有种比较臃肿、很重的感觉。</p>
<p>OrbStack 是一个不错的轻量化替代方案。</p>
<p>注：M 系列 Mac 是 Arm 架构，实际测试下来有很多容器程序可能没有非 x86 镜像</p>
<h3><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_8.png" alt="image-20250329121810545">Git</h3>
<p>代码版本管理。安装 Git，配置 commiter 信息。</p>
<pre><code class="language-bash">git config --global user.name &quot;coin&quot;
git config --global user.email &quot;liaoguoyin#live.com&quot;

</code></pre>
<h3>VS Code</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_9.png" alt="Xnip2024-11-21_23-40-59"></p>
<ul>
<li>
<p>安装 code 命令行快速启动。运行 VS Code，打开命令面板（<code>command + shift + p</code>）输入 <code>Shell</code> 找到「Shell 命令: 在 PATH 中安装 code 命令」</p>
</li>
<li>
<p>安装插件</p>
</li>
</ul>
<h3>Typora</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_10.png" alt="Xnip2024-11-21_23-39-14"></p>
<ul>
<li>Markdown 编辑器。所见即所得。</li>
</ul>
<h3>Raycast</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_11.png" alt="image-20250329142107898"></p>
<p>替换 Spotlight 的瑞士军刀，一个软件能平替好几个软件。年度值得订阅的软件。</p>
<ul>
<li>
<p>记录剪切板历史并在设备间同步，替换 Paste、PasteNow</p>
</li>
<li>
<p>文字 OCR，替换微信 OCR</p>
</li>
<li>
<p>整段翻译，替换欧陆词典、DeepL</p>
</li>
<li>
<p>窗口管理，替换 Magnet</p>
</li>
<li>
<p>OpenAI Chat 能力</p>
</li>
<li>
<p>简单计算器，汇率实时转换</p>
</li>
</ul>
<h3>HeyNote</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_12.png" alt="image-20250329133624445"></p>
<ul>
<li>文本暂存。临时存放一些代码片段、待办事项</li>
</ul>
<h3>Windows App</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_13.png" alt="image-20250329141007877"></p>
<ul>
<li>远程桌面连接客户端，支持 RDP VNC 协议，微软为果子倾情打造</li>
</ul>
<h3>Adobe</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_14.png" alt="image-20250329142542463"></p>
<ul>
<li>
<p>视频剪辑：After Effect，Premiere Pro</p>
</li>
<li>
<p>图片处理：PhotoShop，Lightroom</p>
</li>
</ul>
<h3>Figma</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_15.png" alt="image-20250329140437622"></p>
<ul>
<li>原型设计工具。轻量 P 图</li>
</ul>
<h3>~TeamViewer（已切换为 RustDesk + 自建中转节点）~</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_16.png" alt="image-20250329143354474"></p>
<ul>
<li>
<p>远程协助。帮朋友修修电脑软件文件</p>
<ul>
<li>
<p>可能会有被判断为商用然后被断开链接的问题，但通过远程组网不走 TeamViewer 服务器来解决</p>
</li>
<li>
<p>通过走组网连接，TeamViewer 体验非常好</p>
</li>
</ul>
</li>
</ul>
<h3>LocalSend</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_17.png" alt="image-20250329140258702"></p>
<ul>
<li>跨平台文件传输工具。可以在局域网中多个设备传文件：比如 Ubuntu 传 Mac，Android 传 iPhone</li>
</ul>
<h3>Spotify</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_18.png" alt="image-20250329140735278"></p>
<ul>
<li>
<p>听歌的。</p>
<ul>
<li>
<p>跨平台体验好，能用手机控制同账号下的其他设备端播放器</p>
</li>
<li>
<p>开放能力好，还能通过 API 进行一些插件的开发（比如获取正在听的歌，切歌等操作）</p>
</li>
<li>
<p>曲库还算完整。~比如能听某些404的歌~</p>
</li>
</ul>
</li>
</ul>
<h3>1Password</h3>
<p><img src="https://cdn.liaoguoyin.com/images/new-mac-setup-configuration-guide_19.png" alt="image-20250329141605255"></p>
<ul>
<li>密码管理工具。用了这个软件之后就几乎没记过密码，也不用担心被撞库了。个人觉得最值得花钱的软件，~GitHub Education EDU 还能白嫖~</li>
</ul>
<h2>数据迁移</h2>
<h3>微信</h3>
<blockquote>
<p>2025年9月16日更新，微信4.0和3.*大版本变更，这两个版本中用户聊天文件夹结构发生了变化，以下迁移方法失效。但可以曲线迁移：先装 <a href="https://support.qq.com/products/292433/faqs-more?id=115035https://dldir1v6.qq.com/weixin/mac/WeChatMac_10_15.dmg">3.8.10版本的微信</a>，按照以下目录迁移数据，再更新到4.0，官方4.0微信在初始化时会运行内部迁移数据的逻辑，<a href="https://support.qq.com/products/292433/faqs-more?id=115035">具体操作参考官方文档。</a></p>
</blockquote>
<p>小而美的数据还是太多了，手机容量不够电脑来凑，消息同步到电脑上是个不错的选择。</p>
<p><strong>备份：</strong><a href="https://blog.waynecommand.com/post/wechat-mac-backup">Mac 微信文档存储在</a> 以下目录，退出微信，压缩打包存档。<code>~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9</code></p>
<p>**恢复：**重装 macOS 和微信之后，先运行一次微信并退出，初始化创建相关文件夹后，替换当前目录 <code>2.0b4.0.9/</code></p>
<h3>Chrome 浏览器</h3>
<ul>
<li>
<p>插件配置</p>
<ul>
<li>
<p>油猴插件：屏蔽内容农场，自定义一些本地脚本优化网页</p>
</li>
<li>
<p>沉浸式翻译：按需无感翻译</p>
</li>
<li>
<p>CookieCloud：多端 Cookie 同步</p>
</li>
<li>
<p>Wappalyzer：网站技术栈查看</p>
</li>
<li>
<p>Memos：快速发送笔记到 self-memos</p>
</li>
</ul>
</li>
<li>
<p>Chrome 多用户登录</p>
<ul>
<li>
<p>工作马甲</p>
</li>
<li>
<p>生活日常马甲</p>
</li>
</ul>
</li>
</ul>
<h3>Mail</h3>
<p><img src="https://static.gridea.dev/98cd32d9-2e67-4904-bba1-f2457817463a/3UupwpWwt.png" alt="">邮件客户端用来用去还是自带的 Mail.app 最好用，唯一缺点就是每次换设备需要手动登录。</p>
<h3>SSH Config</h3>
<p>备份恢复 SSH Config: ~/.ssh/config，并恢复公私钥：</p>
<pre><code class="language-markup">Host xxx
    User root
    HostName x.x.x.x
    Port 22
    TCPKeepAlive yes
    ServerAliveInterval 30
    IdentityFile ~/.ssh/id_rsa

</code></pre>
<h2>总结</h2>
<p>对于系统配置、开发工具、应用软件，每个人的偏好和需求肯定不尽相同，按需调整即可。</p>
<p>啰嗦一句，保持良好的备份习惯尤其重要，配置什么的都是随便把玩的，但数据才是核心的。</p>
<p>不想捣鼓这些怎么办，Time Machine，你值得拥有。</p>
]]></description>
            <link>https://old.liaoguoyin.com/new-mac-setup-configuration-guide</link>
            <guid isPermaLink="false">8893a457-496c-414f-8ae8-6bf87af45986</guid>
            <pubDate>Wed, 26 Mar 2025 11:47:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[在 2025 年写博客是为了什么]]></title>
            <description><![CDATA[<p>2018 年，我注册了域名，写下了第一篇 Blog，当时的契机其实是模仿。一天大半夜，我刷到一个本科老大哥的博客，很崇拜他在学校做的事——写了个 iOS App。我把他写 iOS App 的几篇文章翻来覆去地看了好几遍还找到一些隐藏彩蛋，备受鼓舞，发了封邮件而且很快收到了回复。看完更是感觉自己浑身上下有使不完的牛劲，也能做点什么。于是第一步，照猫画虎搭起了学着老大哥的博客准备记录点东西。谁知道学没学到精髓，几年下来，博客程序换了一套又一套，正经文章却没写几篇。</p>
<p>最近几年 LLM 发展很快，极大程度改变了我们绝大多数人的搜索、思考、输出的方式。每个人每天都对着聊天框地抠着提示词，一个字都不愿多打就按下了回车，聊天框呼呼地滚着「答案」，这种状态下，静下来思考成了一种奢侈品，写文章更是让人觉得是一件投入产出比性价很低的事。</p>
<p>所以，2025 年，写博客/写作是为了什么？</p>
<p><strong>我的答案是：是让自己安静下来，梳理在做的事情，是在对着外界倒点垃圾</strong></p>
<h2>是让自己安静下来，梳理在做的事情</h2>
<blockquote>
<p>写点东西可以冷静下来，集中注意力干点事</p>
</blockquote>
<p>在这个碎片化时代，我们的时间、精力、注意力也被敲碎成一块又一块。其实用不上什么下一代计算平台（Vision Pro 🤡），我们已经足够沉浸在手机世界里面，你可以现在抬头看窗外路过的人，会发现 60%+ 的年轻人都会一边走路一边刷着手机。</p>
<p>说来惭愧，我也是这群人的一员。不过是经常下班之后躺着刷，刷完了没有尽头的 Twitter/Rednote timeline，放下手机后一种空虚和焦虑的感觉席卷而来，缓过神来之后却又不知道自己要干什么。很长一段时间，感觉很难集中注意力，思维混乱，梳理不清东西。</p>
<p>写文档确实能让人冷静下来，因为写作本质还是脑力活动，不冷静下来去思考，没法写。</p>
<p>让我领略到这一点的是 L。一次我们一起梳理技术细节，他在共享文档上码了好多字，但在总结部分总觉得没写好，我看他二话不说直接删了全部重写。一开始不是很理解为什么这么干，这不是给自己找堵吗？后来我知道了，看到他从头重新捋了一遍，越梳理越清楚，才明白他是在通过写文档梳理思绪。</p>
<h2>是在对着外界倒点垃圾</h2>
<blockquote>
<p>To be a creator but not user</p>
</blockquote>
<p>前面有一段时间，我每天都逼着自己都在互联网上一些犄角旮旯随地大小💩，希望能每天都输出点东西。比如是发一张当天用心构图的照片、分享一些最近发现的新鲜玩意、写点什么感悟总结。</p>
<p>这么做的理由其实很简单，因为我不想只做一个内容消费者，我想创造点什么，如果恰好创造的东西不是垃圾，那就再好不过了。</p>
<p>坚持这样一件事情确实会让当下的感觉更丰富一些了，我会刻意去感受，去记录，去分享。不过这件事情我并没有坚持太久，直接原因是不好统一量化/记录我每天都发了什么（🤔）。不过我觉得这是一件有意义的事，后面还会尝试一下记录输出。</p>
<p>另一方面，LLM 冲击着我们的信息世界，它们生成的一波又一波的垃圾真正流回互联网，成为以后的训练推理数据，搞不好下一代就是 AI 学出来的数据教出来的。前段时间还看到 WDT 数字生命，大概是用自己的录音、视频、博客去训练了一个和自己语气很像的数字 Bot。</p>
<p>在这种背景下，个人的思考、文档、文章成了一种奢侈品，是最宝贵的数据。不过也是在这种背景下，虽然现在 LLM 遍地开花，纯手工写作成了件奢侈事，你随便整理点思路，它就能给你造一篇像样子的东西出来，但这都是从模型参数下吐出来的东西，不是我们自己思考的结果。相比之下，自己思考和沉淀的东西可能才是非常宝贵的财富。</p>
<h2>期望</h2>
<blockquote>
<p>避免「对完美有执念，对完成没态度」</p>
</blockquote>
<p>从 2018 年到现在，个人记录了 950+ 备忘录，但流水账居多，大都没有发出来过（今天找借口了吗 🥵）</p>
<p>备忘录里面躺了太多垃圾都不如的信息废料。想一想就遗憾，确实错过了很多记录思考的机会。</p>
<p>其实写作应该是件很容易有反馈的事情，只要你不去想还有几个二级标题下面的内容没写完。一直打字写，写，写，它就总有写完的时候。写到这里算是想明白了，以后我的 Blog Post 里面，不应该再有 Draft 和 Done 两种状态。</p>
<p>要开坑就应该一鼓作气地开，先把垃圾框架发出来，坑再慢慢补。这么做的道理其实也很简单，好的文档/文章从来都不是写一次就能写好的，让我们造点垃圾。</p>
<p>这不，刚刚又造完一篇垃圾。不过感觉，好像也没想象中那么垃圾 🤔。</p>
<p>于 2025 年 3 月 14 日 01:01</p>
]]></description>
            <link>https://old.liaoguoyin.com/why-blog-in-2025-reflection</link>
            <guid isPermaLink="false">ffb74dda-4d29-4c8f-bb3d-60ca22514729</guid>
            <pubDate>Thu, 13 Mar 2025 17:03:38 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Zerotier 组网之自建 Moon 加速节点]]></title>
            <description><![CDATA[<p>ZeroTier 是商业级的 P2P 组网方案。因其服务器位于境外，中国大陆用户经常遇到延迟较高的问题。为解决这个问题，<a href="https://docs.zerotier.com/roots">从 ZeroTier 1.2.0 版本开始，引入了自建 Moon 节点功能</a>，允许用户部署私有中转节点来优化网络性能。</p>
<p>本文记录了 Moon 节点的部署和使用步骤。</p>
<h2>1. Moon 节点工作原理概述</h2>
<p>ZeroTier 网络中的所有节点默认都位于 Planet 中，即 ZeroTier 的官方根服务器网络。Moon 节点作为用户自定义的根服务器，具有以下特点：</p>
<ol>
<li><strong>双重路由机制</strong>
<ul>
<li>节点同时使用 Planet 和 Moon 服务器</li>
<li>自动选择延迟最低的路由</li>
<li>当两者都不可用时回退到官方服务器</li>
</ul>
</li>
<li><strong>适用场景</strong>
<ul>
<li>优化特定地理位置的网络性能</li>
<li>创建离线/内网组网环境（毕竟整个互联网也算是个大局域网）</li>
</ul>
</li>
</ol>
<h2>2. 部署 moon 节点</h2>
<p>前置条件：</p>
<ul>
<li>节点需要具有静态 IP（可以是公网 IP 或内网 IP。如果没有公网 IP，无法通过路由公网访问）</li>
<li>开放 UDP 9993 端口（防火墙和安全组都需要放行，可在服务器防火墙和云服务商安全组中放行）</li>
<li>主要需求是稳定的网络连接</li>
</ul>
<h3>2.1 安装 Zerotier</h3>
<p>打开命令行，在云服务器 linux 上安装 Zerotier：</p>
<pre><code>
# Linux 下载安装

curl -s https://install.zerotier.com/ | sudo bash

# 验证安装情况

zerotier-cli info

</code></pre>
<p><img src="https://cdn.liaoguoyin.com/images/zerotier-custom-moon-acceleration-node_1.png" alt="image-20241204225936251"></p>
<h3>2.2 生成 moon 配置文件</h3>
<pre><code>
# 进入配置目录

cd /var/lib/zerotier-one

# 生成初始配置。读取 identity.public 中的公钥信息，生成初始的 moon 节点配置的 JSON 文件:

zerotier-idtool initmoon identity.public &gt; moon.json

</code></pre>
<blockquote>
<p>如果没有生成 moon.json 文件，可能是因为没有写文件的权限。可以 sudo -i 切换为超级用户后再执行命令</p>
</blockquote>
<p><img src="https://cdn.liaoguoyin.com/images/zerotier-custom-moon-acceleration-node_2.png" alt="image-20241204230001959"></p>
<pre><code>
# 生成最终配置文件。基于 moon.json 配置文件，生成最终的 moon 配置文件（通常命名为 000000xxxx.moon）：

zerotier-idtool genmoon moon.json

</code></pre>
<p><img src="https://cdn.liaoguoyin.com/images/zerotier-custom-moon-acceleration-node_3.png" alt="image-20241204230044465"></p>
<p>如上图，最终的 000000a62f602019.moon，其中包含了完整的配置信息：</p>
<ul>
<li>是二进制文件，不可读</li>
<li>带有加密签名</li>
<li>通常放在 /var/lib/zerotier-one/moons.d/ 目录下</li>
<li>moon-id 为(10 位或 16 位标识符)：<strong>000000a62f602019</strong>或<strong>a62f602019</strong></li>
</ul>
<h2>3. 使用 Moon 节点加速</h2>
<p>同样地，首先需要在客户端安装 Zerotier，然后在在客户端中加入 moon 节点。</p>
<h4>3.1 在客户端添加 moon 节点</h4>
<blockquote>
<p>不同客户端版本 moon-id 不一致，需要检查本机 zerotier 版本（zerotier-cli -v 命令可查看版本），如：</p>
<ul>
<li>在 zerotier 1.10.6 版本中，moon-id 是 16 位，前面有多余 0</li>
<li>在 zerotier 1.12.2 版本后，moon-id 是 10 位字符串</li>
</ul>
<p>总的来说，在新版中 moon-id 是 10 位字符串，需要注意删除前面多余的 0</p>
<p>这是一个很奇怪的更新，在官方的文档中我没找到任何明确的说明，<a href="https://github.com/zerotier/ZeroTierOne/issues/2197">还是自己去 Github 开 Issues 问到的（笑脸黄豆.jpg）</a></p>
</blockquote>
<pre><code>
zerotier-cli orbit &lt;moon-id&gt; &lt;moon-id&gt;

</code></pre>
<p>注意版本差异：</p>
<ul>
<li>1.10.6 版本：使用 16 位 moon-id（含前导零）</li>
</ul>
<pre><code>
zerotier-cli orbit 000000a62f602019 000000a62f602019

</code></pre>
<ul>
<li>1.12.2+ 版本：使用 10 位 moon-id（去除前导零）</li>
</ul>
<pre><code>
zerotier-cli orbit a62f602019 a62f602019

</code></pre>
<p>orbit 命令会返回 200 状态码表示添加成功。如果 orbit 一直返回 404 状态需要检查上文的版本，多重试几次 orbit</p>
<h4>3.2 检查所添加的 moon 状态</h4>
<p>可以通过 listmoons 和 listpeers 检查 moon 状态</p>
<h4>3.2.1 检查 moons 列表</h4>
<p>listmoons 命令可以列出本机所加入的自建 moons 节点情况</p>
<pre><code>
zerotier-cli listmoons

</code></pre>
<p><img src="https://cdn.liaoguoyin.com/images/zerotier-custom-moon-acceleration-node_4.png" alt="image-20240530080516172"></p>
<p>上图可以看到加入 moons 前后 listmoons 情况。如果 listmoons 不为空，即表明添加成功</p>
<h4>3.2.2 检查 peers 状态</h4>
<p>listpeers 命令可将本机所加入的网络组下、组网环境中的节点列出，可用于检查机器间的连接情况</p>
<pre><code>
zerotier-cli listpeers

</code></pre>
<p><img src="https://cdn.liaoguoyin.com/images/zerotier-custom-moon-acceleration-node_5.png" alt="image-20240530081529008"></p>
<p>其中最后一个字段 <code>&lt;role&gt;</code>表明机器身份：</p>
<ul>
<li>PLANET 表示 zerotier 官方的中转节点，延迟 42364ms</li>
<li>MOON 表示自建 zerotier 中转节点，延迟 55ms</li>
<li>LEAF 表示组网范围内的其他同级别节点</li>
</ul>
<p>通过在两台 LEAF 节点中同时添加 MOON 节点，可以实现大幅加速效果</p>
<blockquote>
<p>确定添加完成后。可在添加 moon 节点后观察组网机器内的机器延迟情况（ping 测试）观察中转效果</p>
<p>加入前后两机之间的 ﻿ping 时间明显减少，500ms+ -&gt; 50ms+</p>
</blockquote>
<p>其他指令：</p>
<p>-<code>zerotier-cli deorbit &lt;moon-id&gt;</code>可用于取消加入 moon 节点</p>
<ul>
<li><code>zerotier-cli -h</code>，帮助，可查看所有指令</li>
</ul>
<h2>4. 最佳实践</h2>
<ul>
<li>建议客户端至少加入两个 Moon 节点</li>
<li>Moon 节点专注于网络中转功能
<ul>
<li>避免将多个节点部署在同一物理服务器上</li>
<li>避免将 Moon 服务器同时作为 Moon 和 Leaf 节点</li>
<li>适合将 Moon 部署在就近的数据中心（如国内云服务商）</li>
</ul>
</li>
</ul>
<h2>5. Ref</h2>
<ul>
<li><a href="https://docs.zerotier.com/roots/">Private Root Servers | ZeroTier Documentation</a></li>
<li><a href="https://github.com/zerotier/ZeroTierOne/issues/2197">MoonID 变更情况，新机器无法加入 moon-id 的方案</a></li>
<li><a href="https://docs.zerotier.com/roots">Private Root Servers</a></li>
</ul>
]]></description>
            <link>https://old.liaoguoyin.com/zerotier-custom-moon-acceleration-node</link>
            <guid isPermaLink="false">60996542-17bd-4b35-a35c-c0f11d87d011</guid>
            <pubDate>Wed, 27 Nov 2024 08:46:38 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[记 Docker 容器内外文件夹权限一致性问题的坑]]></title>
            <description><![CDATA[<p>最近频繁使用 docker-compose，发现在容器迁移、备份和使用方面非常便利。</p>
<p>容器的启停只需要 <code>docker-compose up</code> 和 <code>docker-compose down</code>，大大降低了心智负担。</p>
<p>但是在一次使用 <code>docker-compose up</code> 启动（创建）以下容器时，遇到了创建文件 ./data/config.json 失败的问题。</p>
<pre><code># https://hub.docker.com/r/shellngn/pro/
services:
  shellngn:
    image: shellngn/pro:latest
    container_name: shellngn
    ports:
      - &quot;8080:8080&quot;
    volumes:
      - ./data:/home/node/server/data
    environment:
      - HOST=0.0.0.0
      - TZ=Asia/Shanghai
    restart: unless-stopped
</code></pre>
<p>通过 <code>docker logs shellngn</code> 查看日志，发现是挂载本地目录 ./data 到容器后，容器在创建文件时报错：</p>
<pre><code>shellngn is up-to-date
Attaching to shellngn
shellngn    | node:internal/fs/utils:356
shellngn    |     throw err;
shellngn    |     ^
shellngn    | 
shellngn    | Error: EACCES: permission denied, open './data/config.json'
shellngn    |     at Object.openSync (node:fs:596:3)
shellngn    |     at Object.writeFileSync (node:fs:2322:35)
shellngn    |     at _0xb7420b.&lt;computed&gt;.&lt;computed&gt; [as init] (/home/node/server/bundle.js:1:1213484)
shellngn    |     at 40392 (/home/node/server/bundle.js:1:1069259)
shellngn    |     at _0x2d71f3 (/home/node/server/bundle.js:1:9475095)
shellngn    |     at /home/node/server/bundle.js:1:9475859
shellngn    |     at /home/node/server/bundle.js:1:9483783
shellngn    |     at Object.&lt;anonymous&gt; (/home/node/server/bundle.js:1:9483984)
shellngn    |     at Module._compile (node:internal/modules/cjs/loader:1364:14)
shellngn    |     at Module._extensions..js (node:internal/modules/cjs/loader:1422:10) {
shellngn    |   errno: -13,
shellngn    |   syscall: 'open',
shellngn    |   code: 'EACCES',
shellngn    |   path: './data/config.json'
shellngn    | }
shellngn    | Node.js v18.20.1

</code></pre>
<p>查阅资料后发现，这个问题通常是 <strong>Docker 容器内外的操作用户权限不匹配</strong> 导致的：</p>
<ul>
<li>当使用 <code>docker-compose up</code> 创建文件夹时，这些文件夹通常是由容器内部的进程创建的。容器内部的进程可能以 root 用户或其他特定用户身份运行，而这个用户的 UID（用户 ID）可能与宿主机系统上的用户权限不匹配，这就导致了权限错误</li>
<li>相比之下，如果使用 Docker Volume 就不会有文件权限的问题。因为 Docker 守护进程创建文件夹时，它通常能够正确处理权限问题</li>
</ul>
<h2>解决方法</h2>
<p>解决方法其实非常简单：限制、同步权限</p>
<h3>方法一：使用 user 指令</h3>
<p>在 docker-compose.yml 文件中，指定容器以哪个用户/用户组运行：<strong>user: &quot;${UID}:${GID}&quot;</strong></p>
<pre><code>...省略...
version: '3'
services:
  your_service:
    image: your_image
    user: &quot;${UID}:${GID}&quot;
    volumes:
      - ./data:/app/data
...省略...
</code></pre>
<h3>方法二：使用 Docker Volumes</h3>
<p>使用 Docker Volumes 让 Docker 服务自动完成权限管理（但 volumes 迁移不是很方便，用起来不如文件夹挂载灵活）</p>
<pre><code>...省略...
version: '3'
services:
  your_service:
    image: your_image
    volumes:
      - myvolume:/app/data
volumes:
  myvolume
...省略...
</code></pre>
<h2>排查问题</h2>
<p>问题解决后，很疑惑 <strong>Docker 容器内外的操作用户权限不匹配</strong> 背后到底是什么不匹配。问题出现后，进行了如下排查：</p>
<ul>
<li>
<p>查看文件夹用户组和权限，发现容器新建出来的文件所属用户及用户组是 1000:1000</p>
</li>
<li>
<p>为了查看容器的操作用户，查看了容器镜像配置 <code>docker inspect shellngn</code></p>
<p>发现 ID 1000 用户在容器内对应着 node 用户</p>
<p><img src="https://cdn.liaoguoyin.com/images/docker-file-permission-consistency-issues_1.png" alt="image-20241010010359365"></p>
</li>
<li>
<p>在宿主机和容器中命令行中输入 id 查看默认用户，发现 ID 默认运行用户都是 0 号用户（root 用户）</p>
<ul>
<li>
<p>宿主机中 /etc/passwd 文件里没有 1000 用户</p>
<p><img src="https://cdn.liaoguoyin.com/images/docker-file-permission-consistency-issues_2.png" alt="image-20241010010707953"></p>
</li>
<li>
<p>容器中 /etc/passwd 中有 1000 用户，且就是上文的 node 用户</p>
<p><img src="https://cdn.liaoguoyin.com/images/docker-file-permission-consistency-issues_3.png" alt="image-20241010010218935"></p>
</li>
<li>
<p>尝试在宿主机新建 ID 为 1000 的用户：<code>useradd -u 1000 coincoin</code></p>
</li>
<li>
<p>再执行没有传入 user 的 docker-compose.yaml 新建成功，且所属文件是宿主机 ID 1000 的用户（刚创建的用户 coincoin）</p>
<p><img src="https://cdn.liaoguoyin.com/images/docker-file-permission-consistency-issues_4.png" alt="image-20241010010556788"></p>
<p><strong>在容器中：文件夹的权限在容器内外所属于的用户/用户组可能不一致，文件其实属于各自域内的对应 ID 的用户，认 ID 而不认用户名。上文最开始遇到的问题中，这些文件属于 ID 1000 的用户，但在宿主机中不存在这个 ID 的用户，因此报错。</strong></p>
</li>
</ul>
</li>
<li>
<p>此外，通过 <code>docker history shellngn</code> 查看镜像层发现：容器镜像默认用户在创建文件夹之前就被切换为了 node（USER node）</p>
<p><code>&lt;missing&gt;      2 months ago    /bin/sh -c #(nop)  USER node                    0B</code></p>
<p><img src="https://cdn.liaoguoyin.com/images/docker-file-permission-consistency-issues_5.png" alt="image-20241204000404832"></p>
<p>至此真相大白，容器在创建文件夹时创建的用户在映射到外部时无法根据内部的用户 ID 找到对应的用户，导致文件操作没有权限。</p>
</li>
</ul>
<p>在容器中，这种切换操作用户是一种安全手段，通过切换到非特权用户来运行应用程序，减少潜在的安全风险。Node.js 官方镜像就经常使用这个做法。但这可能会导致上文这样的潜在权限问题。</p>
<h2>总结</h2>
<p>总的来说，<strong>Docker 容器内外的操作用户权限不匹配</strong> 是指在运行容器时，内外操作的用户不一致，导致的文件夹权限归属无法确认。因此：</p>
<ul>
<li>尽量保持容器内外用户 ID 的一致性。在 docker-compose.yml 文件中，可以指定容器以哪个用户/用户组运行：<strong>user: &quot;${UID}:${GID}&quot;</strong></li>
<li>在使用挂载目录时要考虑权限映射问题</li>
<li>可以根据具体需求选择使用 user 指令或 Docker Volumes</li>
</ul>
<h2>参考资料</h2>
<ul>
<li><a href="https://docs.docker.com/reference/dockerfile/#user">Docker Dockerfile USER 指令官方文档</a></li>
<li>ChatGPT</li>
</ul>
]]></description>
            <link>https://old.liaoguoyin.com/docker-file-permission-consistency-issues</link>
            <guid isPermaLink="false">71b53fbe-37ff-44f4-9646-3bae3b4a0071</guid>
            <pubDate>Sat, 09 Nov 2024 08:47:02 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[顺手发了篇爆款小红书技术文章所想到的]]></title>
            <description><![CDATA[<p>秋招好累，昨天下午实在无心学习，顺手写了个小红书抢亚运会转售票文章<a href="https://www.xiaohongshu.com/explore/65100bc0000000001f03eaaa">《网页端半自动抢转售票心得》</a></p>
<p>早上一睁眼发现满是红点，不到 12 个小时就 1w 阅读，1k 多收藏，从 0 新涨了一小波粉丝</p>
<p><img src="https://cdn.liaoguoyin.com/images/viral-xiaohongshu-tech-article-insights_1.png" alt="xiaohongshu-data"></p>
<p>明明一个没有技术含量的东西（一句话就是浏览器插件自动刷新提醒库存方便抢票），效果却出奇地好 🤨</p>
<p>搞得我洗澡的时候想了好久：为什么？还能复刻吗？能尝试做点什么吗？</p>
<h2>为什么效果还不错？</h2>
<p>因为大家迫切地想解决抢转售票手机刷买不到的问题。</p>
<ul>
<li>这个方法算是 <strong>一点小小的优化</strong>，能解决到别人的痛点，且学习成本低，用户体验好</li>
<li>行文逻辑里面放满了最后抢成功的图刺激他们的痛点神经，包括后面在评论区里面不断鼓励他们去做，有人跟着做着成功了来评论一下，效果正向反复促进</li>
<li>推荐算法牛逼，能帮你推送到目标用户，对他们直接产生价值</li>
<li>对用户来说能省下不少给黄牛的钱，对黄牛来说是致命的，甚至能催生新的代抢新黄牛</li>
</ul>
<h2>还能复刻吗？</h2>
<p>可以，但是要降低身段，放弃所谓的技术洁癖，做有价值的内容，不需要炫技。</p>
<p><strong>做所谓的目标人群觉得有价值的内容，让他们觉得看了会有收获、能被激励到</strong></p>
<p><strong>快速扫两眼了解到的符合他们点开标题前的期望阈值，用户就会收藏或者点赞，文章权重就会上来，然后有更多的推荐流量，这和做 SEO 一个道理</strong></p>
<h2>还有什么要注意的？ 一些思考</h2>
<p>本来为了满足一点小小的虚荣心撒狗粮，在文章里面小秀了一下是给对象抢的出发点。</p>
<p>但是过了一点时间发现评论区好像有疑似 👊 言论</p>
<p><img src="https://cdn.liaoguoyin.com/images/viral-xiaohongshu-tech-article-insights_2.png" alt="1695615908864"></p>
<blockquote>
<p>这个评论甚至是现在下面热度最高的，老实说，我本人看到这个没那么舒服。作为一个简单的攻略分享，我其实并没有这个方向的想法和立场。是我玻璃心了？不过，想了一下对我也没什么实质影响，也就没想那么多了，可能这就是现在互联网冲浪的常态吧</p>
</blockquote>
<p>倒是这让我后背一凉，但凡我这个文章没有对他们有帮助就完蛋了，言论很大程度会被带偏翻车的</p>
<p>之前和专门做新浪微博自媒体的朋友聊过，她说很多人为了博流量造对立，让普通用户在评论区吵架然后博主直接给账号带权重 😅</p>
<p>我个人觉得，这种东西意味着：<strong>有很多小心思要谨慎地表达，因为这会被互联网这种增益器无限放大，这种流量肯定是一玩就火的（有对立有评论有热度也就有了推荐），反噬也是肯定的</strong></p>
<h2>能尝试做点什么吗？有什么经验</h2>
<p>可以尝试一下，但是要缕清楚目标用户，要缕清楚方向保持一致性，最重要的是要行动起来。</p>
<ul>
<li>**多尝试多发。**本来这个抢票方法我 9 月 20 日 就实践了，当时就寻思要不要发一下，效果还很好其实想帮朋友抢一下，但是好像大家不感兴趣，犹豫了一下搞然后没发，昨天实在无聊发了一下，效果确实超乎想象，勇敢的 XXXXXX（摊手），本来今天下午有个面试我也是紧张到不行也不想写这个总结的，还是想着赶紧记录一下
-<strong>技术是为业务服务的</strong>。如果不能更好地解决问题，似乎再牛的技术也没什么不得了的（尝试麻痹自己 Doge</li>
<li>**要想清楚目标用户。**举个简单的例子，这种没有技术含量的东西放在搞技术的人面前根本不值得一提，但是对小红书里面天天刷抢票的人来说简直是福音，现在帖子下面还有来还愿的，成就感++
-<strong>要保持内容方向，才会有粘性。</strong> 打造所谓的人设，做一个垂直方向，花心思想一下方向，其他的东西不要涉及，<strong>尤其比较容易引战的内容</strong></li>
<li><strong>行文逻辑需要锻炼下</strong>。本来写完了帖子让 GPT 4 润色了下，结果发现机翻味浓厚，唯一的作用就是加上了一些 emoji 😈，《金字塔原理》还得看完</li>
</ul>
<p>关于实际落地的内容，要从如何让用户感觉到学到了东西或者能学到东西想一下，不要太犹豫，主要是要赶紧行动，“小步快跑，快速迭代&quot;</p>
<p>另外一些简单经验可以参考我之前做网易云自媒体号的体会<a href="https://liaoguoyin.com/post/livebot-netease-music/">《在云村搬运视频的一年多所感受到的》</a></p>
<p>回想起来，网易云之前做了粉丝大概 9k，前阵子还做个微信工具公众号粉丝 9k，bilibili 做视频剪辑挂演唱会直播累计播放量也有几十万</p>
<p>越来越觉得这种东西做起来是有套路的，无非是用有价值的内容换用户流量、换粉丝</p>
<p>所谓的流量有什么用我到现在还没想清楚，很多人张口闭口就是变现，但是我觉得不是，分享才是互联网最开始的愿景，出发点可能是分享点有用的东西，也可能是想找点乐子，和网友产生一点特别的链接，然后才是那些附加而来的东西</p>
<p>一开始就想着比较利益的东西总感觉做起来会很受阻碍，扯淡扯远了（doge</p>
<h2>Recap</h2>
<p>回到开头的问题。效果为什么好？</p>
<p><strong>一方面是方法确实简单奏效，内容有直接价值，另一方面就是大数据推送的目标用户精准，这对后面做内容方向是有启发性的</strong></p>
<p>最后的最后。对我个人来说，通过这个经历，更重要的是收获了丰富的正反馈和满满的自信。秋招的颓废（短暂地）一扫而光，虽然俺创新属性普通，但是俺组合能力 Max 啊，起码用奇思妙想用各种工具，或者二开各种东西去解决实际问题的能力还是过了平均线的</p>
<p>这让我有那么几分钟的错觉：秋招如果实在找不好合适的工作也不至于饿死，因为日常这种小把戏我还挺多的</p>
<p>本科的时候也好几次听几个同学说“有问题找 LGY”，我家狗子也说我比许愿池里面的王八好使（也不知道是夸还是骂 MD）</p>
<p>虽然我听了嘴上骂骂咧咧又被白嫖了，但是打心里面，其实吧，蜜罐子都打翻了 🤣</p>
]]></description>
            <link>https://old.liaoguoyin.com/viral-xiaohongshu-tech-article-insights</link>
            <guid isPermaLink="false">2cabe0bb-6059-4f5c-a1f3-2a00d4b1b6c1</guid>
            <pubDate>Mon, 25 Sep 2023 03:34:01 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[通过值传递、引用传递理解C++函数花里胡哨的写法]]></title>
            <description><![CDATA[<p>最近在 <a href="https://cplusplus.com/doc/tutorial/">CPlusPlus Tutorial</a> 上发现一篇很棒的文章，很大程度上帮助我理解了 C++ 函数一堆标识符写法的用意：</p>
<pre><code class="language-c++">inline string concatenate(const string&amp; a, const string&amp; b)
{
  return a+b;
}
</code></pre>
<p>C++ 函数参数是值传递还是引用传递？函数声明中给参数加上 &amp; 是什么操作？参数带 const 又是什么意思？inline 关键字又是干嘛的？</p>
<p>本来想提炼一下，发现还是摘抄 <a href="https://cplusplus.com/doc/tutorial/functions/">原文</a> 然后对段翻译，才能尽可能地原汁原味。（部分意译，水平有限，读不通可以看该段下方的原文。）</p>
<h2>参数的值传递和引用传递</h2>
<blockquote>
<p>Arguments passed by value and by reference</p>
</blockquote>
<p>在前面（节选）函数中已经看到，参数总是通过值传递（译者注：数组除外，数组作为参数传递的是数组首地址指针，默认为引用传递）。这意味着，在调用函数时，传递的是调用瞬间各参数的值，这些参数的值被拷贝到函数参数变量中。例如：</p>
<blockquote>
<p>In the functions seen earlier, arguments have always been passed by value. This means that, when calling a function, what is passed to the function are the values of these arguments on the moment of the call, which are copied into the variables represented by the function parameters. For example, take:</p>
</blockquote>
<pre><code class="language-c++">nt x=5, y=3, z;
z = addition(x, y);
</code></pre>
<p>在这个例子中，函数 addition() 传入了 5 和 3，也就是 x 和 y 值的拷贝。这些值（5 和 3）被用于初始化在函数中定义过的变量。在函数中修改这些变量并不会对函数外部的 x 和 y 变量产生影响，因为 x 和 y 本身并不被传递到函数中，那函数调用那一刻，传递的只是它们值的拷贝。</p>
<blockquote>
<p>In this case, function addition is passed 5 and 3, which are copies of the values of x and y, respectively. These values (5 and 3) are used to initialize the variables set as parameters in the function's definition, but any modification of these variables within the function has no effect on the values of the variables x and y outside it, because x and y were themselves not passed to the function on the call, but only copies of their values at that moment.</p>
</blockquote>
<p>然而，在某些情况下，需要从函数内部访问外部变量。为达成此目的，参数应该通过引用传递而不是值传递。例如，此代码中 duplicate() 函数拷贝了其三个参数的值，导致用作参数的变量实际上在调用中被修改：</p>
<blockquote>
<p>In certain cases, though, it may be useful to access an external variable from within a function. To do that, arguments can be passed by reference, instead of by value. For example, the function duplicate in this code duplicates the value of its three arguments, causing the variables used as arguments to actually be modified by the call:</p>
</blockquote>
<pre><code class="language-c++">// passing parameters by reference
#include &lt;iostream&gt;
using namespace std;

void duplicate(int&amp; a, int&amp; b, int&amp; c)
{
  a*=2;
  b*=2;
  c*=2;
}

int main()
{
  int x=1, y=3, z=7;
  duplicate (x, y, z);
  cout &lt;&lt; &quot;x=&quot; &lt;&lt; x &lt;&lt; &quot;, y=&quot; &lt;&lt; y &lt;&lt; &quot;, z=&quot; &lt;&lt; z;
  // output: x=2, y=6, z=14
  return 0;
}
</code></pre>
<p>为了能存取参数，函数参数应该声明为引用。在 C++ 中，通过在参数类型后紧跟 ampersand(&amp;) 符号来表示引用，如上例中 duplicate() 函数的参数。</p>
<blockquote>
<p>To gain access to its arguments, the function declares its parameters as references. In C++, references are indicated with an ampersand (&amp;) following the parameter type, as in the parameters taken by duplicate in the example above.</p>
</blockquote>
<p>当变量被作为引用传递时，传递的就不再是拷贝，而是变量本身。在函数参数中标识的变量，以某种形式和传递给函数的参数相关联，对其在函数中相应局部变量的任何修改都会反映到调用时传递的变量中。</p>
<blockquote>
<p>When a variable is passed by reference, what is passed is no longer a copy, but the variable itself, the variable identified by the function parameter, becomes somehow associated with the argument passed to the function, and any modification on their corresponding local variables within the function are reflected in the variables passed as arguments in the call.</p>
</blockquote>
<p>事实上，a, b, 和 c 在函数调用 (x, y, and z) 时，成为了参数的别名，在函数中对变量 a 的任何修改，都将改变其对应的外部变量 x 上，任何对 b 的修改都会影响 y，对 c 的修改都会影响 z。这也是为什么在上面的例子中，函数 duplicate() 修改了 a, b, 和 c 的值后，x, y, 和 z 也被影响了。</p>
<blockquote>
<p>In fact, a, b, and c become aliases of the arguments passed on the function call (x, y, and z) and any change on a within the function is actually modifying variable x outside the function. Any change on b modifies y, and any change on c modifies z. That is why when, in the example, function duplicate modifies the values of variables a, b, and c, the values of x, y, and z are affected.</p>
</blockquote>
<p>如果没有把 duplciate() 定义为：</p>
<blockquote>
<p>If instead of defining duplicate as:</p>
</blockquote>
<pre><code class="language-c++">void duplicate(int&amp; a, int&amp; b, int&amp; c) 
</code></pre>
<p>而将其定义为没有 ampersand 引用符(&amp;)的（函数）：</p>
<blockquote>
<p>Was it to be defined without the ampersand signs as:</p>
</blockquote>
<pre><code class="language-c++">void duplicate(int a, int b, int c)
</code></pre>
<p>变量也就不会是引用传递了，而将会创建对应的值拷贝。在这个例子中，程序的输出是没有被修改过的 x, y 和 z(i.e., 1, 3, 7)</p>
<blockquote>
<p>The variables would not be passed by reference, but by value, creating instead copies of their values. In this case, the output of the program would have been the values of x, y, and z without being modified (i.e., 1, 3, and 7).</p>
</blockquote>
<h2>效率考量和常量引用</h2>
<blockquote>
<p>Efficiency considerations and const references</p>
</blockquote>
<p>调用有参函数会进行值拷贝。对基本数据类型如 int 来说，操作开销是尚且低廉（可接受）的，但若参数是较大的复合类型，将无疑会导致一些开销。例如，看一下这个函数：</p>
<blockquote>
<p>Calling a function with parameters taken by value causes copies of the values to be made. This is a relatively inexpensive operation for fundamental types such as int, but if the parameter is of a large compound type, it may result on certain overhead. For example, consider the following function:</p>
</blockquote>
<pre><code class="language-c++">string concatenate(string a, string b)
{
  return a+b;
}
</code></pre>
<p>函数持有两个 string 型参数（按值），并返回连接字符串的结果。当函数被调用时，参数值拷贝，a 和 b 参数都被值拷贝。如果是两个很长的字符串，会在调用时拷贝大量数据。但是，可以通过传递引用来避免拷贝（开销）。</p>
<blockquote>
<p>This function takes two strings as parameters (by value), and returns the result of concatenating them. By passing the arguments by value, the function forces a and b to be copies of the arguments passed to the function when it is called. And if these are long strings, it may mean copying large quantities of data just for the function call. But this copy can be avoided altogether if both parameters are made references:</p>
</blockquote>
<pre><code class="language-c++">string concatenate(string&amp; a, string&amp; b)
{
  return a+b;
}
</code></pre>
<p>传递引用参数不需要拷贝。函数直接在传入的字符串别名上进行操作，就像把指针传递给函数一样。这样一来，因为没有字符串拷贝开销，拼接字符串引用的版本比拼接字符串值的版本将更有效率。</p>
<blockquote>
<p>Arguments by reference do not require a copy. The function operates directly on (aliases of) the strings passed as arguments, and, at most, it might mean the transfer of certain pointers to the function. In this regard, the version of concatenate taking references is more efficient than the version taking values, since it does not need to copy expensive-to-copy strings.</p>
</blockquote>
<p>反过来说，带引用参数的函数通常会对传入的参数进行修改，这是引用参数的设计初衷。</p>
<blockquote>
<p>On the flip side, functions with reference parameters are generally perceived as functions that modify the arguments passed, because that is why reference parameters are actually for.</p>
</blockquote>
<p>解决的办法是让函数保证引用参数不会被本函数修改，这可以通过将参数限定为常量来实现：</p>
<blockquote>
<p>The solution is for the function to guarantee that its reference parameters are not going to be modified by this function. This can be done by qualifying the parameters as constant:</p>
</blockquote>
<pre><code class="language-c++">string concatenate(const string&amp; a, const string&amp; b)
{
  return a+b;
}
</code></pre>
<p>通过将参数限定为常量，函数将无法修改 a 或 b 的值，但实际上可以作为引用（参数的别名）直接访问它们的值，而不必实际拷贝字符串的值。</p>
<blockquote>
<p>By qualifying them as const, the function is forbidden to modify the values of neither a nor b, but can actually access their values as references (aliases of the arguments), without having to make actual copies of the strings.</p>
</blockquote>
<p>因此，常量引用提供了类似于按值传参的功能，对大型参数来说效率更高。这也是 C++ 中复合类型参数流行的原因。但是请注意，对大多数基本数据类型来说，效率没有明显提升，在有的情况下，常量引用甚至可能导致效率更低。</p>
<blockquote>
<p>Therefore, const references provide functionality similar to passing arguments by value, but with an increased efficiency for parameters of large types. That is why they are extremely popular in C++ for arguments of compound types. Note though, that for most fundamental types, there is no noticeable difference in efficiency, and in some cases, const references may even be less efficient!</p>
</blockquote>
<h2>内联函数</h2>
<blockquote>
<p>Inline functions</p>
</blockquote>
<p>调用函数通常会导致一定开销（栈参数，jump，等等）。因此，如果函数很短，将代码简单地插入到函数调用的地方，相比于正常调用函数可能更有效率。</p>
<blockquote>
<p>Calling a function generally causes a certain overhead (stacking arguments, jumps, etc...), and thus for very short functions, it may be more efficient to simply insert the code of the function where it is called, instead of performing the process of formally calling a function.</p>
</blockquote>
<p>在函数声明前加上一个 inline 标识符告知编译器：对这个函数，应该（倾向于）使用内联展开而不是通常的函数调用。这样做并不会改变函数功能，而是建议编译器：将函数体生成的代码插入到函数的各调用点上，而不是常规地进行函数调用。举个例子：上述 concatenate() 函数可以被内联声明为：</p>
<blockquote>
<p>Preceding a function declaration with the inline specifier informs the compiler that inline expansion is preferred over the usual function call mechanism for a specific function. This does not change at all the behavior of a function, but is merely used to suggest the compiler that the code generated by the function body shall be inserted at each point the function is called, instead of being invoked with a regular function call. For example, the concatenate function above may be declared inline as:</p>
</blockquote>
<pre><code class="language-c++">inline string concatenate(const string&amp; a, const string&amp; b)
{
  return a+b;
}
</code></pre>
<p>这将告知编译器，当 concatenate() 被调用时，程序倾向将该函数内联展开，而不是采用常规的函数调用。内联只在函数声明时指定，而不在调用时。</p>
<blockquote>
<p>This informs the compiler that when concatenate is called, the program prefers the function to be expanded inline, instead of performing a regular call. inline is only specified in the function declaration, not when it is called.</p>
</blockquote>
<p>注意，大多数编译器会对代码进行优化：尽管没有显示地标记 inline 标识符，当编译器看到提升效率的机会时，也会使用内联优化。因此，这个标识符只是表明编译器对此函数倾向于内联，真正内不内联还得看编译器。在 C++ 中，优化是委托给编译器的任务，编译器可以自由地生成任何代码，只要表现结果符合预期。</p>
<blockquote>
<p>Note that most compilers already optimize code to generate inline functions when they see an opportunity to improve efficiency, even if not explicitly marked with the inline specifier. Therefore, this specifier merely indicates the compiler that inline is preferred for this function, although the compiler is free to not inline it, and optimize otherwise. In C++, optimization is a task delegated to the compiler, which is free to generate any code for as long as the resulting behavior is the one specified by the code.</p>
</blockquote>
<h2>recap</h2>
<p>个人觉得这篇文章的层层递进娓娓道来的感觉非常妙，很多语言层面更新出来的特性并不是凭白无故的，如果能抽离一些主要适用场景来阐述，摸清它的设计目的，理解起来就会非常舒服，记忆和使用自然也就不是特别难的事。</p>
<p>回到最开始的问题：</p>
<p>C++ 函数参数是值传递还是引用传递？函数声明中给参数加上 &amp; 是什么操作？参数带 const 又是什么意思？inline 关键字又是干嘛的？</p>
<ol>
<li>C++ 没有限定的普通函数和标识符的形式参数的情况下，默认是<strong>值传递</strong>（数组除外）。也就是在函数调用传入参数时会进行值拷贝，在函数内部对参数的一些操作并不会影响到函数外，因为函数调用结束，拷贝的参数也就从帧栈上释放。</li>
<li>通过在函数声明处对函数进行 &amp; 标识符进行限定，对传入的变量转为<strong>引用传递</strong>，拷贝的仅仅只是参数的单个指针，<strong>避免值拷贝造成的内存开销</strong>，也能很方便地传入复合数据类型指针。</li>
<li>形式参数带上 const，是因为直接通过 &amp; 标识符传入的参数极有可能在函数体内部被修改，<strong>我们想要利用 &amp; 来只传递引用，又不想传入的参数被修改，导致函数对外有副作用，所以给参数加上一个 const，确保函数不能修改传入的参数</strong>。</li>
<li>那为什么又来个 inline 关键字呢，这是为了给编译器提供一种编译倾向，<strong>使得编译器能针对一些短小的函数进行内联展开的编译优化，避免常规函数调用时的帧栈和函数跳转开销</strong>。</li>
</ol>
<p>最后，最佳实践也就相当容易理解了：</p>
<pre><code class="language-c++">inline string concatenate(const string&amp; a, const string&amp; b)
{
  return a+b;
}
</code></pre>
<p>理解可能还不够深刻，如果描述有误，请留言拍砖。</p>
<p>最后再次初学 C++ 的朋友隆重推荐 CPlusPlus Tutorial.</p>
<h2>ref</h2>
<ul>
<li><a href="https://cplusplus.com/doc/tutorial/">CPlusPlus Tutorial</a></li>
<li><a href="https://cplusplus.com/doc/tutorial/functions/">Functions</a></li>
</ul>
]]></description>
            <link>https://old.liaoguoyin.com/best-practice-for-cpp-function</link>
            <guid isPermaLink="false">8146a82b-2fb8-4878-9a8b-fd5029ad7182</guid>
            <pubDate>Tue, 14 Jun 2022 06:29:48 GMT</pubDate>
        </item>
    </channel>
</rss>