做这个项目纯属被自己的注意力涣散给逼出来的。作为一个需要长期对着屏幕写东西、看数据、回消息的人,我发现自己每天真正能沉下心干活的时间,其实少得可怜。手机是静音的,通知是关掉的,但电脑上那些隐形的“分心陷阱”——某个社交网站的小红点、某个新闻弹窗、甚至只是顺手点开的某个视频标签页——都能让半小时的工作被拉伸成三个小时。我试过各种时间管理App、番茄钟、白噪音,但问题是:提醒结束的时候很爽,提醒开始的时候却管不住自己。所以后来我想,干脆做一个叫“buzz”的小工具,它不搞花哨统计,不搞积分激励,唯一的任务就是在你分心的瞬间,用一次短促的“嗡”声把你拽回来。这个项目很小,但做完之后我发现,它背后的逻辑和很多“自我管理类工具”完全不一样——它不是靠“自律”逼你,而是靠“条件反射”改变你。这篇文章就把我从立项到落地、从踩坑到打磨的全过程拆开来讲,如果你也有类似的需求,或者也想自己动手写一个类似的效率小工具,可以直接照着复现。
1. 项目整体设计与思路拆解
先说说“buzz”这个项目的核心定位。我给它定了一句话:一个只负责发出“提醒性嗡嗡声”的桌面端轻量工具。它的核心应用场景有两个:一是你在写代码、写文档、做设计等需要连续专注的工作时,它会默默在后台运行,一旦检测到“当前活动窗口切换到娱乐、社交、新闻等非工作类应用”,就立刻播放一声短促的蜂鸣;二是配合番茄钟使用——你专心工作了25分钟,它会用一声长音告诉你“可以休息了”,但休息结束后,你回到工作状态时,它又会用一声短音确认“回来了”。这两个场景,本质都是利用声音信号形成一个“无意识提醒”,不需要你去看任何面板,也不需要你手动去按什么键。
当时我给自己列了几个硬性要求,现在回头看,这些要求决定了整个项目的走向:
- 足够轻量,内存占用尽量控制在50MB以内,不能像某电邮箱一样动不动上百MB;
- 启动之后必须安静,没有主窗口,只在菜单栏或系统托盘放一个小图标;
- 声音要短、要干净,最好是一声类似老式电话震动或电子表闹铃的蜂鸣,而不是一段音乐;
- 判定“分心”的逻辑要简单粗暴,宁可有误报,也不能漏掉真正的分心时刻;
- 不用联网,不做云同步,不收集任何数据,所有记录只留本机;
- 最好可以跨平台——因为我主力机是Windows,但有时也会用macOS写东西。
基于这几个要求,我开始在“技术选型”上做取舍。这里有必要说一个我后来踩了坑才悟出来的道理:这种小工具,千万不要选“重框架”。一开始我天真地想过用Electron来做,因为前端资源多、界面好画。但Electron本身就是个浏览器套壳,随便一个空项目内存轻松上200MB,完全违背了我“轻量”的初衷,而且它对于一个“没有主界面”的托盘程序来说,纯属杀鸡用牛刀。所以我很快排除了它,转向了Python加原生系统API的方案。
选Python的原因其实也很朴实:写检测窗口和播放声音的逻辑,Python有几个很成熟的库可以直接调系统底层接口,而且代码量很短;加上我自己的主力开发环境一直有Python 3.10在跑,省去了一堆环境折腾。播放声音这一块,Windows下可以用现成的winsound库,macOS下可以用afplay命令,都不用额外装什么东西。检测当前窗口名字,Windows下可以用pywin32的GetForegroundWindow加GetWindowText;macOS下可以用AppKit里的NSWorkspace获取当前激活的应用名。这样下来,项目本身不用引任何重型第三方依赖,唯一的重量级依赖就是pywin32和pyobjc,也都是各自平台上的标准绑定库。
但光有用Python写的核心逻辑还不够。我同时想清楚了另一个关键点——这个项目一定要有“配置自由度”。每个人的“分心应用列表”完全不同:有人刷B站是分心,有人看GitHub是工作;有人看新闻是分心,有人做舆情监测恰恰需要挂着新闻页面。所以我把“什么是分心应用”设计成了一个可编辑的清单,而不是硬编码写死在代码里。用户自己创建一个简单的文本文件,一行一个应用名或窗口标题关键词,程序启动时读取这个清单,运行时逐秒检查当前活动窗口,一旦匹配,就触发提醒。这个设计后来被证明非常关键,因为很多人拿到工具第一反应就是问“能不能把XX加进去?”,有了这个清单,用户根本不用等作者更新版本,自己就能适配自己的使用习惯。
整个项目一开始拉起来的骨架是这样的:
buzz/ ├── buzz_client.py # 主程序入口,负责事件循环、窗口检测、声音触发 ├── config.ini # 配置文件,包含音量、检测间隔、声音类型等 ├── blocklist.txt # 分心应用关键词清单,一行一个关键词 ├── sounds/ # 存放提示音的目录,默认提供两种音效 │ ├── tap.wav │ └── long.wav ├── README.md └── requirements.txt这个结构非常简单,但是足够跑通一个“最小可用版本”。我在整个项目里都不打算引入所谓的“工程化体系”,连setup.py都没写,因为它的定位就是“复制到任何一台电脑上,装好依赖就能跑”,而不是一个需要被分发安装的正式产品。换句话说,我写它是为了“自己用着爽”,其次是让有类似需求的人能拿到源码,看懂、改改就能用。
2. 核心细节解析与实操要点
“buzz”这个项目看着简单,但真正动起手来,有几个核心细节必须处理好,否则你会得到一个不是吵死自己、就是根本不起作用的废物工具。我踩了不少坑,现在把它们拆开来说重点。
2.1 声音本身:蜂鸣不是“叫声越大越好”
声音是整个工具的“输出媒介”,也是最容易被忽略、却最深影响体验的部分。很多人做提醒工具,喜欢直接放一段轻快的MP3音乐,或者干脆用系统默认的“叮咚”声。但在实际专注场景下,音乐只会让你更分心,默认系统提示音又会和微信、短信混淆,导致大脑很快脱敏——听到“叮”一声,你以为有人找你,顺手就切去微信了,等你发现只是buzz在提醒你,又已经过了半分钟。
所以我设计了两个完全不同的音效:一个是“短促蜂鸣”,时长控制在0.3秒左右,频率在1000Hz上下,听起来像一个非常短促的“嘀嗒”;另一个是“双段长音”,用于番茄钟结束,频率相同但连续响两下,每段0.5秒,中间隔0.2秒。短音的作用是打断你,长音的作用是宣告阶段结束。两者有明确的区分度,不会混淆。
在实现上,我没有用什么音频合成库,而是直接生成两个WAV文件放在sounds/目录里。生成WAV的代码其实非常简单,甚至不需要图形界面,几行Python就能搞定,核心就是用wave和struct模块把正弦波数据写进去。这里有个经验:人耳对1000Hz左右的纯音最敏感,但连续播放超过2秒就会开始烦躁,所以提醒音越短越好,宁可多几次短提醒,也不要一次放个3秒钟的绵长音效。
代码大致长这样:
import wave import struct import math def generate_tone(filename, frequency=1000, duration=0.3, volume=0.5, sample_rate=44100): with wave.open(filename, 'w') as f: f.setnchannels(1) # 单声道就够,没必要立体声 f.setsampwidth(2) # 16位PCM f.setframerate(sample_rate) for i in range(int(duration * sample_rate)): # 简单的正弦波,没有任何包络处理,短音够用 value = int(volume * 32767.0 * math.sin(2 * math.pi * frequency * i / sample_rate)) f.writeframes(struct.pack('<h', value)) generate_tone('sounds/tap.wav', duration=0.3) # 短促蜂鸣 generate_tone('sounds/long.wav', duration=0.5, volume=0.45) # 长音的基础音实际使用时,我在“长音”的实现里做了一点变化:用两个连续的tap.wav中间加0.2秒静音拼在一起,而不是单独生成一个1.2秒的长音文件。这样做的好处是,当用户觉得长音太吵时,只要调整短音的音量,长音也会跟着变,不用分别调两套音量。这个设计思路是从“统一音源”的实践里来的,也算是后期迭代的一个小优化。
播放声音的代码在每个平台上略有差异。Windows下,我直接用标准库的winsound.PlaySound('sounds/tap.wav', winsound.SND_FILENAME | winsound.SND_ASYNC),这里注意一定要加SND_ASYNC,否则播放声音的调用会阻塞主循环,导致下一轮检测被延后。macOS下我就直接调用命令行播放器:
import subprocess import time def play_tone(path): # macOS用afplay播放,异步运行 subprocess.Popen(['afplay', path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # Windows下用winsound的异步模式 if os.name == 'nt': import winsound def play_tone_sync(path): winsound.PlaySound(path, winsound.SND_FILENAME | winsound.SND_ASYNC) else: def play_tone_sync(path): subprocess.Popen(['afplay', path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)这里有一个很重要的领悟:很多“效率工具”做残了,不是核心逻辑不对,而是反馈感太滞后。如果你按下开始键2秒钟后才出声音,大脑根本建立不了“切到豆瓣=马上有干扰声”的条件反射。所以在设计时,我把检测间隔压得很短,甚至可以让用户自行设定到500毫秒一次。一旦切到分心页面,几乎在瞬间就会响起来,没有任何迟疑。这种“瞬时反馈”是工具能起效的前提。
2.2 分心检测:怎么判断“你正在做正事”
“buzz”最核心的技术判断是:怎么知道用户当前正在用哪个应用,以及怎么判断这个应用属于“分心类”。这部分是很多类似工具设计得最粗鲁的地方——有的直接统计你整个系统的使用时长,有的要求你手动填写“工作开始时间”,有的干脆用摄像头看你是否坐在屏幕前。这些方案要么太笨重,要么太侵犯隐私。
我最后用的是最简单也最有效的一个办法:读取当前“处于焦点的顶层窗口标题”,然后和分心关键词清单做匹配。
在Windows下,这段逻辑用pywin32就能实现:
import win32gui def get_active_window_title(): hwnd = win32gui.GetForegroundWindow() return win32gui.GetWindowText(hwnd)在macOS下,则用AppKit和Quartz配合:
from AppKit import NSWorkspace from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID def get_active_window_app(): ws = NSWorkspace.sharedWorkspace() return ws.activeApplication()['NSApplicationName']但这里有一个很现实的坑:窗口标题比应用名更需要做敏感词匹配。为什么?因为同一个Chrome窗口,可能同时开着工作邮件和微博。如果你只判断“当前激活的应用是Chrome”,那就没法区分用户是在查资料还是在摸鱼。所以检测分心不能只靠“应用名”,还得结合“窗口标题里的关键词”。
我的匹配逻辑是两级判定:
- 第一级,窗口标题或应用名里是否存在明确的工作关键词,比如“工单”、“需求”、“日报”、“Postman”、“VS Code”、“Word”、“Excel”等,如果有,直接视为“工作状态”,不触发提醒;
- 第二级,如果不在工作关键词里,再去匹配分心清单
blocklist.txt里的关键词,只要命中任意一个,就触发提醒。
这种“白名单先行、黑名单兜底”的设计,比单纯的黑名单要人性化得多。举个例子:你的分心清单里写了“Bilibili”,但如果你恰好用Bilibili查一个开源项目的视频教学,而那个视频标题里含有“教程”两个字,那么第一级工作关键词会把“教程”命中,从而不触发提醒。虽然这个逻辑不完美,但“宁可不提醒,也不要误提醒”,因为误提醒会让你把工具关掉,而漏提醒只是让你偶尔多划几次水。两害相权,取轻。
在实际实现时,我会同时读取“应用名”和“窗口标题”,把它们连成一个字符串后统一做关键词匹配。代码用一个简单的循环遍历清单即可,不需要引入多少复杂的规则引擎——小工具就要有“小”的样子,能用线性扫描解决的,就不上正则,能用正则解决的,就不上机器学习。
2.3 配置文件的取舍:先做出来,再改舒服
项目里必须有配置文件,但如果你一开始就想着做一套“界面精美、选项极多”的配置面板,大概率会陷入改动代码两小时、调界面一晚上的泥潭。我的做法很简单:用config.ini管全局参数,用blocklist.txt管分心关键词,所有配置都允许热加载。
config.ini里的主要参数有:
[general] check_interval_ms = 800 ; 检测间隔,单位毫秒,越小越灵敏,但更费CPU volume = 0.5 ; 音量,0到1之间 remind_tone = tap.wav ; 分心提醒音 finish_tone = long.wav ; 番茄钟结束音 [work_keywords] enabled = true ;; 注意:分号后面是注释 ;; 可自行增删,一行一个,命中任意一个即视为“工作状态”我做了定期重读配置文件的设计,每次检测循环跑完一轮就检查一下文件的修改时间,一旦发现更改就重新加载。这样做的好处非常实际:你不需要为了改一个关键词而重启整个进程,随时打开blocklist.txt加一行,下一轮检测就能生效。这个“热加载”设计在初期其实没做,后来是因为我自己嫌重启麻烦才加的,但实测下来非常值得——很多用户拿到工具后第一件事就是想自己定制关键词,如果每次都要改代码重启,传播难度就会大得多。
还有一个小细节:check_interval_ms我建议设置在500到1000毫秒之间,太短了会让CPU风扇狂转,太长了会让提醒变得迟钝。我之前试过100毫秒的间隔,结果在开发机上CPU占用飙到了8%以上,这还只是一个检测程序,完全不可接受;后来调到800毫秒,CPU使用率降到0.3%以下,感知上几乎没有延迟。这个平衡点,各位在实际使用中可以根据自己电脑的配置来微调。
3. 实操过程与核心环节实现
讲完设计,来说实际动手的过程。这一部分我尽量像当时写代码那样,按步骤走,每个环节都给出可以直接参考的实现方式。
3.1 环境准备与启动逻辑
先交代环境:Windows 11专业版,Python 3.10.9,没有用conda,直接在全局环境装依赖。我建议你也用Python 3.10或3.11,Python 3.12在某些老的pywin32版本上可能会有兼容性问题,别给自己找麻烦。
安装依赖只要两行命令:
pip install pywin32 # macOS用户则执行: # pip install pyobjc如果你的系统里同时装了多个Python版本,记得确认pip指向的版本和启动脚本时用的Python是同一个,不然会反复出现ModuleNotFoundError。这个坑我刚开始就踩过,后来学乖了,直接用python -m pip install来避免歧义。
启动逻辑上,我期望的是“双击即运行,全程无感”。所以在主入口处做了以下几件事:
- 检查配置文件是否存在,如果不存在就自动生成一套默认值;
- 启动一个系统托盘图标,方便用户随时退出或重启检测;
- 进入事件循环,一直轮询当前窗口状态直到用户手动退出。
这里要特别说一下托盘图标。很多命令行程序退出只能去任务管理器里找进程,这很不方便。pywin32自带一个win32gui的托盘消息循环示例,我照着改了一个精简版,只保留一个“退出”菜单。macOS下用rumps库也能轻松创建菜单栏应用,不过那个库是第三方依赖,我当时为了“轻”没有引入,只是在macOS上用一个带菜单的pyobjc原生实现,稍微多一点代码,但能少维护一个依赖。
托盘部分的Windows实现大致是这样:
import pythoncom import win32gui import win32con class TrayApp: def __init__(self, title='buzz'): self.title = title self.hwnd = None self.tray_id = None def create(self): message_map = { win32con.WM_DESTROY: self.on_destroy, } self.hwnd = win32gui.CreateWindow( 0, 'buzzTrayWindow', self.title, 0, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, None, 0 ) win32gui.UpdateWindow(self.hwnd) self.tray_id = 1 win32gui.Shell_NotifyIcon( win32con.NIM_ADD, ( self.hwnd, self.tray_id, win32gui.LoadIcon(0, win32con.IDI_APPLICATION), 0, 0, 0, self.title ) ) # 简化版的菜单只有“退出” menu = win32gui.CreatePopupMenu() win32gui.AppendMenu(menu, win32con.MF_STRING, 1024, '退出') win32gui.Shell_NotifyIcon(win32con.NIM_ADD, self._notify) pythoncom.PumpMessages()这段代码其实没那么难理解,它就是借用系统托盘API挂一个图标,然后进入消息泵循环。如果你对Windows底层不熟悉,照抄即可,暂时不用深究。
3.2 检测循环:从“获取窗口”到“发提醒”的主流程
主循环是整个程序的灵魂。我用循环加sleep的方式实现,没有用复杂的定时器回调。每轮依次执行以下操作:
- 读取当前活跃窗口的标题和所属应用名;
- 检查“工作关键词”白名单,命中就跳过;
- 检查“分心关键词”黑名单,命中就触发提醒;
- 记录一条本机日志,方便事后复盘;
- 休眠指定毫秒数,进入下一轮。
判断逻辑经过几次迭代,最终长这个样子:
import time from threading import Lock def run_loop(stop_event, config, blocklist_manager): while not stop_event.is_set(): try: title, app = get_active_window_info() if not title and not app: time.sleep(0.5) continue combined = f"{app} {title}" # 1. 白名单优先 if config.is_work_context(combined): # 记录一条最近工作信息,为将来扩展做准备 state.last_work_time = time.time() time.sleep(config.check_interval_ms / 1000.0) continue # 2. 黑名单匹配 if blocklist_manager.is_distracted(combined): if time.time() - state.last_alert_time > 5: play_tone(config.remind_tone) state.last_alert_time = time.time() log_distraction(combined) time.sleep(config.check_interval_ms / 1000.0) except Exception as e: debug_print('loop error', e) time.sleep(1)这里有几个细节值得单独说明。
首先,last_alert_time这个变量是后来才加的,目的是做防重复提醒。如果用户切到了分心页面,但没有切走,那理论上每一轮检测都会命中黑名单,如果每次都响,人很快就崩溃了。所以我设定了一个5秒的冷却时间——同一轮“持续分心”状态下,最多每5秒提醒一次。实际上,这个提醒频率也偏高,后来我建议用户把冷却时间调到15秒左右,保留“被拉住”的感觉又不会让人想砸电脑。
其次,log_distraction这个日志函数很有用。它会把每次触发提醒的时间、当时的窗口标题记到一个文件里。晚上复盘的时候,你可以清楚地看到自己一天到底摸了多少次鱼、都在哪里摸的。我有一个朋友用这个日志做出一份奇葩周报,把自己每天分心次数最多的三个应用标黄,然后和团队说“这是我本周要消灭的三个目标”,一下子把效率工具玩成了团队KPI。
还有一点,try-except块的粒度要注意。我不建议在每一轮循环里使用巨大的try-except包住所有逻辑,否则一旦某轮抛出异常,你可能一直处于错误恢复的循环里。只捕获你预期可能出现的异常,比如窗口句柄获取失败、文件被占用等,其余的让它彻底暴露出来,至少能让开发者看到清晰的报错信息。
3.3 用“彩排”模式跑通全流程
工程师有个习惯,写完代码第一件事不是“真刀真枪干”,而是先跑一遍“彩排”。我的做法是在程序里内置一个“演示模式”(参数--demo),它不进行真实窗口检测,而是模拟一段预定的窗口切换序列,比如“工作5秒、切到B站3秒、工作3秒、切到豆瓣5秒”,然后观察提醒是否在正确的时机触发。
这个模式帮了我大忙——它排除了“环境问题引起的假失败”。比如有一次,我明明写了提醒逻辑,但运行时死活不出声。彩排模式下很快就发现问题:声音文件用了相对路径,但工作目录不是项目根目录,导致路径找不到。这种bug在真实场景下很难排查,还可能被误认为是“窗口检测没生效”。
彩排模式的实现也简单,无非是循环里读一串预定的假窗口数据,走一遍完整的判定流程。但它带来的价值是巨大的:任何一次修改,都可以在2分钟内验证预期行为,而不是改一点、真摸鱼试半天。
4. 常见问题与排查技巧实录
这把工具自己做了不少天,也把自己当小白鼠使用了不短时间。中间遇到的问题五花八门,但大多是同类工具使用者会普遍踩中的,我整理成了表格,方便直接对照。
4.1 典型问题速查表
| 问题 | 可能原因 | 排查与解决方式 |
|---|---|---|
| 无论怎么操作都不出声 | 音量设置为0、winsound在远程桌面下不可用、音频文件路径错误 | 先跑一遍--demo彩排模式,若能出声说明主逻辑没问题;检查config.ini里的volume;在本地桌面重新登录试一次 |
| 提醒太频繁,吵得没法干活 | 冷却时间设置太短;分心清单里含有工作也会出现的通用词 | 把check_interval_ms调到800以上,把冷却时间调到10秒以上;清理blocklist.txt,去掉“Chrome”这类太泛的关键词 |
| 切到分心页面后没有反应 | 白名单把当前内容误判成工作状态;窗口标题关键词匹配不到 | 检查work_keywords白名单,临时全部清空测试;换用更精确的分心关键词,比如把“B站”换成具体UP主名称或频道关键词 |
| CPU占用很高,风扇狂转 | 检测间隔设得太短 | 把check_interval_ms从100改到500~1000毫秒;检查是否有旧进程重复运行,任务管理器里应该只有一个buzz进程 |
macOS下用了afplay但播放有延迟 | 有些macOS版本的声音服务初始化较慢 | 在程序启动时先播放一次静音WAV,预热音频服务;或者改用PyObjC的NSSound播放,延迟会更低 |
| 日志文件越来越大,磁盘吃紧 | 没有做日志轮转 | 加一段“超过5MB自动清空或按日期切割”的逻辑;如果你只想用几天,直接手动删掉日志文件即可 |
4.2 几个毫无保留的独家避坑细节
文件名里的关键词,一定要“去空格、统一大小写”。Windows的窗口标题里经常有全角空格、“-”和“—”,如果你在
blocklist.txt里写的是“哔哩哔哩”,而窗口标题里写的其实是“Bilibili”,那就匹配不上。我的做法是在匹配前统一lower()并且把常见标点符号替换成空格,否则很容易出现“明明分心了却不响”的怪现象。系统托盘图标在win11下可能不显示。这是Windows 11“托盘图标折叠”机制带来的改变。如果你在任务栏上看到小箭头里有个隐藏图标,需要手动拖出来。否则你关掉程序时会找不到托盘入口,以为脚本还活着。对应的,我后来在
buzz_client.py里加了一个命令行参数--quit,可以直接通过命令行终止进程,省得每次都去找图标。不要用全局快捷键来暂停检测。很多人会想,“我上洗手间、接电话时不想被提醒怎么办”?一开始我想做一个“暂停30分钟”的热键,但发现热键监听要么依赖第三方库,要么在自己写的消息循环里比较复杂。后来我换了一种更稳的方案:直接在托盘图标菜单里放一个“暂停/恢复”菜单项。用户点一下菜单就切换,不占用快捷键资源,也不会和系统其它软件冲突。这个改动算是从“可用”到“好用”的关键一步。
番茄钟模式和分心提醒联动时,特别注意“阶段切换”的声音重叠。比如番茄钟刚结束时,你正好在分心页面,就会有两段提醒同时响。我的处理方式是给两种声音各设一个独立的“最近播放时间”,互不覆盖,而且设置成如果长音正在播放,那短音会自动延后1秒播出,避免两个音效叠成噪音。
4.3 从“能用”到“愿意用”的路有多长
代码层面,这个项目的功能结构其实半小时就能写完。但从“能用”到“愿意用”,我花了不少心思在各种体验细节上。举几个不太起眼、但缺了就会让人放弃的改动:
- 音量要能独立调。系统音量一更,提醒音就没了或者炸了。我把提示音的音量单独封装在
config.ini里,在播放前用pydub做音量缩放(或者直接在生成WAV时按音量系数生成多个版本),避免受系统音量影响。 - 白天和黑夜的提醒音要不同。这是我自己的怪癖——晚上写东西时听到高频率“嘀嘀”声会格外烦躁,所以我做了一个“根据当前时间自动切换音效”的小功能:18:00之后使用低频一点的蜂鸣,听起来更像闷在枕头下的闹钟,不刺耳。这个功能很小,但真的提升了夜间使用的舒适度。
- 每天第一次触发提醒时,给出一条“一天的总结”。构思很简单:每天早上9点第一次触发提醒前,把昨天日志里分心最严重的三个时间点整理出来,播完提醒音后用OS通知的形式展示一份“昨天你可能在这些时间段走神了”。这算是日志功能的一个自然外延,也帮我把工具从“被动打断”升级成了“定期复盘”,更有长期坚持的动力。
这些细节没有一项是硬性需求,但没有它们,这个项目很可能在第三天就被我卸载了——对于一个效率工具,最致命的问题不是功能弱,而是用户不愿意持续打开。所谓“坚持自律”从来不是靠意志力实现的,靠的是工具把阻力降到最低、把反馈做得恰到好处。
5. 写在最后的一点实践心得
“buzz”这个项目规模不大,但它让我对“做个能改善自己日常习惯的小工具”这件事有了全新理解。很多人以为“效率工具”必须做成一个功能庞大的套件,要有数据看板、要有AI分析、要有团队分享,但真实场景里,一个每天默默在后台运行、从不打扰你、只在关键时刻“嗡”一声的小东西,反而更容易被长期保留。它不给你制造“我建设了完美系统”的错觉,而是像牙医那个小钻头一样,在你的注意力即将溜走的瞬间,轻轻拉你一下。
如果你也想从头做一个类似的工具,我最想让你记住的其实是两件事:第一,先把“最小可用版本”跑起来,再考虑加花活——不要一开始就上Electron、云同步、数据大屏这些重装备,一个检测循环、一个播放声音的函数、一个关键词清单,已经能解决80%的问题;第二,一定要留出“日志”和“配置”这两个口子,一个让你看得见效果、一个让工具能适应你的使用习惯,这两点决定了工具是呆板的死物还是能长久的伙伴。
目前这个版本的“buzz”还在我自己的电脑上跑着,每天都还在产生新的日志,我也还在持续调教它的分心清单。它的代码量很小,没有任何值得夸耀的高深技术,但它确确实实帮我从每天七八个小时的对屏幕发呆,找回了近两小时的干净注意力。这份收益,远比写它的那几百行代码更重。如果读这篇文章的你也正被“注意力碎片化”困扰,不妨动手复刻一下这个项目,或者拿着我上面的思路去改造任何一款你已经觉得“哪里不对劲”的效率工具。很多时候,好的工具不是做出来的,是改出来的。