惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
有赞技术团队
有赞技术团队
博客园 - 司徒正美
量子位
N
News and Events Feed by Topic
T
Threatpost
Last Week in AI
Last Week in AI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
酷 壳 – CoolShell
酷 壳 – CoolShell
C
CERT Recently Published Vulnerability Notes
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
Intezer
人人都是产品经理
人人都是产品经理
T
Tenable Blog
IT之家
IT之家
雷峰网
雷峰网
腾讯CDC
博客园 - 聂微东
V
Visual Studio Blog
S
SegmentFault 最新的问题
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
V2EX
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
T
Tailwind CSS Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
N
Netflix TechBlog - Medium
J
Java Code Geeks
宝玉的分享
宝玉的分享
F
Full Disclosure
WordPress大学
WordPress大学
A
Arctic Wolf
小众软件
小众软件
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
NISL@THU
NISL@THU
AI
AI
Hugging Face - Blog
Hugging Face - Blog
F
Fortinet All Blogs
云风的 BLOG
云风的 BLOG
N
News | PayPal Newsroom
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

少数派

派早报:Google 发布 Fitbit Air 等 - 少数派 「新人报到」確認需求,再開始 - 少数派 从 SOLO 独立开发者社区,我看到了越来越多开发者开始做自己的产品 - 少数派 我怎么管理那些"不常做,但总会忘"的生活事项 - 少数派 人形机器人量产元年,数据才是具身智能的“生死线” - 少数派 BuhoLaunchpad 高度还原 Mac 启动台:开发历程与思考 - 少数派 五年陪伴依然不舍,DIY 换壳后让罗技 MX Master 3 继续服役 - 少数派 新玩意 240|少数派的编辑们最近买了啥? - 少数派 一日一技|为什么你应该关闭 iOS 的键盘声音 - 少数派 我做了个插件和 Skills,一键提取任何网站的设计规范 Design.md - 少数派 住在三四线城市的你,该开始录播客了 - 少数派 甘南秘境,大白高国 - 少数派 AI的审美:谁让把我变成川内倫子 - 少数派 返工怎能不烦恼,打工人片单总有一部是你的「嘴替」 - 少数派 为了让「上厕所」更健康,我做了一个小工具 - 少数派 AI + Skill,能够让生成的文章去除 AI 味吗? - 少数派 新玩意|韶音OpenDots ONE 耳夹式耳机 - 少数派 《美满》| 在每一个春天的晚上相爱(362) - 少数派 新玩意|优篮子 PS01 MagSnap 磁吸支架 - 少数派 自我整合手记 | 我开始早睡了:用稳定规则,为自由托底 - 少数派 用龙虾(OpenClaw)两个多月,我最深的12个体会 - 少数派 听歌时间到,12 张你可能错过的 2025 华语乐坛好专辑 - 少数派 承诺能追吗 - 少数派 macOS 26启动台没了? 我做了个不一样的App启动器 - Keboard - 少数派 《四海为家的人》| INTJ对话INTJ(361) - 少数派 你发过的那些黑历史,是时候一次清干净了 - 少数派 新玩意:安安静静玩,越玩越专注:计客密码机 - 少数派 iPad 用户首次体验 Android 平板:vivo Pad6 Pro - 少数派 数据逻辑强 - 少数派 极北行+ | 一路向北,探访日本至北之地 | 001 - 少数派 万字剖析:千问App深度体验报告(2026) - 少数派 在2026年,如何真正防止别人抄袭你的作品 - 少数派 怎么用 50 块搭个 AI 语音助手?我踩了 3 天坑 - 少数派 YeeroAI:让 AI 对话真正成为知识管理的一部分 - 少数派 爬泰山 - 少数派 「旅图显影」 App 更新:这次,我们补上了一点「手感」 - 少数派 假期出门太折磨?我的 23 条经验帮你规划惬意旅行 - 少数派 工作流会变吗 - 少数派 Claude Opus 4.6 怎么用最省钱?我测了 5 种方案 - 少数派 GPT Image 2 让图文并茂不再稀罕 - 少数派 用户侧出发——什么是AI,我要不要学习? - 少数派 找片、转存、整理、播放一条龙!让你的付费网盘值回票价 - 少数派 欢迎试用!日课一问2.0插件 - 少数派 自己做的MDeditor,原本想购买 Typora 试了两次支付不成功,干脆自己做一个 - 少数派 vibe coding了一个 3MB 的小工具,让 ~/Downloads 彻底告别混乱 - 少数派 因为受不了 Mac 的风扇策略,我做了一个风扇控制工具 - 少数派 别只怪模型 - 少数派 Warp 终端的 AI 功能怎么用?我测了一周的体验 - 少数派 AI 写代码老是出 bug?这 5 个配置我后悔没早知道 - 少数派 「新玩意」苹果出相机可能就这样:Sigma BF + 45mm F2.8 DG Contemporary - 少数派 一个面向2030年的AI操作系统是什么样子的:浅谈cola这款有灵魂的Agent - 少数派 别只看写代码 - 少数派 每天解决10个问题,还是一口气攻坚解决400个? - 少数派 AI 交易机器人怎么搭?我用 Claude 跑了一周实盘 - 少数派 Maptoposter Online:把你爱的城市画成艺术海报 - 少数派 Function Calling 怎么用?我测了 3 个模型发现差距真大 - 少数派 Legend Talk:我做了个 AI 圆桌,让 160 位思想家围着你的问题转 - 少数派 如何找到自己的蓝方?在小县城寻找压力测试 - 少数派 语音输入与软件接口|2026年聊AI时,我们都聊些什么(上) - 少数派 混动已经卖爆,纯电又来补刀——钛7闪充版简直“不讲武德” - 少数派 本月玩什么|朋友收藏、识质存在、沙罗周期 - 少数派 为什么要每天坚持输出? - 少数派 Claude API 挂了好几个小时,你的项目有备用方案吗? - 少数派 Function Calling 没你想的复杂——我用它做了个有点用的工具 - 少数派 登录系统立即播放视频或者图片音乐的软件 - 少数派 我为什么创建 FlipHTML5 下载工具 - 少数派 残局没电?多品牌外设电量统一管理软件EasyBluetooth已支持RTSS游戏内显示以及AIDA64 - 少数派 前往通义路的路 - 少数派 太好看了,媲美Sun的个人导航页,NAS部署星云门户 - 少数派 乌黑嘴唇“一键检测”上线了 - 少数派 派早报:Claude AI 接入多个创意软件生态、FILCO 生产方接手品牌等 - 少数派 【更新】BearCLI、Claude 连接器与 MCP 服务器 - 少数派 记了上千条流水,还是看不懂财务?我做了一个让 AI 读懂账本的工作台 - 少数派 MINI R56 升级原厂 Sport 模式 - 少数派 新玩意 | 一棵柠檬树(仿真版) - 少数派 Momenta的“物理AI”野望,需迈过“含摩量”这道关 - 少数派 网页直接投屏控制手机!NAS一键部署PandaScrcpy,流畅丝滑可远程。 - 少数派 众测|邀你一同探索随身 AI 硬件入口 YoooClaw C·ONE - 少数派 2050大会:分享时间是真诚 参会记 - 少数派 iPad 赋能电影创作:国内首部宣纸手绘长片《燃比娃》的幕后故事 - 少数派 AI的审美:我用 8 个大模型给 100 张旅行照片打分 - 少数派 普通人如何破圈?去参加一个本地协会 - 少数派 把极空间的图标全换了,主题DIY全攻略打造你的专属NAS桌面 - 少数派 电子便签墙,帮你实现便签自由 - 少数派 我如何用三个 CLI 工具取代文档创建需求 - 少数派 原来真的有人可以玩一辈子 - 少数派 社区速递 139 | 派友热议三月买了啥、复古单反尼康 Df 体验 - 少数派 06 作品的赏析与评价 - 少数派 TDS REVIEW|索尼 WF-1000XM6 降噪真无线耳机体验 - 少数派 35.98万起售的第二代腾势D9,我看重的不是堆料,而是不凑合 - 少数派 鼠须管 Squirrel 皮肤配置指北 - 少数派 从watch ultra2换到redmi watch6 - 少数派 派早报:阿里巴巴发布视频生成模型 HappyHorse 1.0 等 - 少数派 别迷信1M - 少数派 家人们天塌了!网盘“大封杀”,多个渠道多条路,NAS部署PanHub - 少数派 AI与人勾心斗角!NAS一键部署AI狼人杀,假日休闲必备。 - 少数派 电商必备!Comfyui工作流批量生图插件,一次生成12张!支持Nano banana pro模型 - 少数派 Comfyui工作流配置Gpt-image-2模型教程,0.03/张 - 少数派 OpenClaw第三方APi怎么配置?可使用Gpt-image-2模型 - 少数派 会员社区话题精选 Ep. 103 - 少数派
像产品设计一样思考像程序员一样执行 - 少数派
2022-05-29 · via 少数派

我很喜欢“工程师”这个叫法,坚持认为绝大部分软件研发从业者都是工程师。工程师,顾名思义就是工程层面的专业人士,核心是解决工程应用层面的问题。
软件工程师是机器算法与现实世界的中间件,更多需要考虑的是用技术来解决现实世界问题。举例,工程师并不需要研发 md5 等算法,但必须掌握如何开发安全的登录功能。云服务的时代,工程师拥有了更强大的能力。

请注意:下文里都统一用**工程师**来称呼软件产品的研发人员,即软件工程师。

回到软件研发本身,工程师就是要做出好产品。软件产品是由代码构成的,好产品一定来自于好代码,但要写出一份好代码,可不仅仅是只懂得写代码就可以的。任何一位工程师,都应该像产品经理一样思考产品需求问题,只有充分掌握了需求,并对于业务需求变化有一定的预判,才能写出好的代码实现。下面用一个工作中出现过的例子来详细解释一下。

问题:Emoji 表情渲染差异

曾经做通讯产品研发时,各种iOS、Android、浏览器等对于 Emoji 渲染有很多差异,同一个 Emoji 表情在不同设备会有较大显示差异,有些老旧设备设置不支持,会直接出现 “”,。

要解决问题,首先得搞清楚 Emoji 渲染差异到底是什么问题。

1. Emoji 是什么?渲染差异又是怎么回事?

调研后的结论摘录

Emoji 是什么?Emoji 是文字,只是长得像图,从提出后,因为表现形式的丰富,越来越流程,Android、iOS、Web等平台也都逐渐进行了支持,同时 Emoji 的数量也在不断的扩充中。

根据标准协议,每个 Emoji 都有唯一的 unicode 码,但是渲染后长什么样子,都是各平台自行实现,苹果、安卓以及各浏览器都分别设计了自己的图标并内置渲染使用,如下图,都不太一样。

V11.0 Lists http://unicode.org/emoji/charts/full-emoji-list.html

还了解到不同图标有着不同的版权声明,学习了很多 Emoji 的掌故,比如小人表情的肤色争议、裸露身体表情与伊斯兰世界禁忌等。

几个需要用到的结论:

  1. Android、iOS 都支持原生 Emoji 渲染
  2. 大部分桌面浏览器都支持原生 Emoji 渲染
  3. 输入法里使用的图标只能是平台默认的
  4. 移动浏览器、小程序环境都支持 原生 Emoji 渲染

2. 设计解决思路

限于篇幅和专业方向仅 web 部分

  1. 使用 unicode 作为 Emoji 唯一标识
  2. 对于支持 Emoji 渲染
  3. 对于不支持 Emoji 的老浏览器,使用图片填充(因为 Emoji 数量太多,只处理常用的 100多个,并对使用者提供新增 Emoji 表情图片的扩展方法),版权是务必要注意的问题
  4. 解决方案分离出来,处理成通用的 sdk,并支持 AMD、CMD协议

这个环节非常难,需要考虑用户体验、技术可行性、性能、成本等因素,但却又非常重要,一定要严格对待认真商讨,定义出一个合理的方案。但凡这个环节没有认真对待的,几乎必然进入无法交付的灾难,问题会蔓延到 QA 等其他团队。

工作到这,具体的问题就搞清楚了,也确定了解决思路。但就我工作中的观察,这恰恰是最缺失的一部分,最后几乎都导致一定程度的灾难。

3. 开发准备工作阶段

先处理好 sdk 的加载调用方法,首先处理 sdk 自身与项目引用的关系

"use strict"; //严格模式

/*
模块化: http://www.ruanyifeng.com/blog/2012/10/javascript_module.html

es6 module: http://es6.ruanyifeng.com/#docs/module
CMD规范: https://github.com/seajs/seajs/issues/242
AMD规范: https://github.com/amdjs/amdjs-api/wiki/AMD

requirejs: http://requirejs.org/docs/whyamd.html
*/

;(function (global, factory,namespace) {
    if(typeof exports === 'object' && typeof module !== 'undefined'){
        module.exports = factory();
    }else if(typeof define === 'function' && define.amd){
        define(factory);
    }else{
        global[namespace] = factory();
    }
})(window, function(){
    /*
    从这里开始写代码
    */
}, namespace);

4. 定义

通过注释把之前思路和调研落实到纸面,个人习惯通过注释加入必要的参考链接

定义需要实现的方法,sdk 对外提供的方法,脑补实现过程做必要的补充,用 return 写一些伪实现

根据业务逻辑粗略定义参数,不需要拘泥于具体的数据类型,只需要跑业务逻辑,关键是快,多快速跑几遍更好

emoji参考资料:

https://github.com/maicong/Twitter-Emoji
http://blog.getemoji.com/post/57054354336/which-browsers-support-emoji

http://caniemoji.com/
https://gist.github.com/mwunsch/4710561

http://www.fileformat.info/info/unicode/char/1f436/index.htm
*/

/* 工具类 */
var utils = {
    subs : function(template, data) {
    },
    getEmoji : function(emojiUnicode){
    },
    errorReport : function(error){
        console.log(error);
    }
};

var isSupport = (function(){
})();

/*emoji支持情况*/
var supportList = {
};    

function getEmojiImage(emojiUnicode){
}

function getEmojiName(emojiUnicode){
}

function getEmojiAuto(emojiUnicode){
}

return {
    utils : utils,
    getName : getEmojiName,
    show : function(emojiUnicode,type){
        if(type == "emoji"){
            return utils.getEmoji(emojiUnicode);
        }else if(type == "image"){
            return getEmojiImage(emojiUnicode);                
        }else{
            return getEmojiAuto(emojiUnicode);
        }
    },
    init : function(config){
        /*
        config = {
            emojis : {
                "u1F600":{},
            },
            size : "20px",
            type : "auto/image/emoji"
        }
        */
    }
}

5. 复核,完成精准定义

复核所有的函数、参数、变量的定义与命名,通过代码+注释明确落实到纸面

有实现的逻辑直接 copy 贴入,无实现的逻辑可以用伪代码实现,确保调用

配合必要的调用示例或单元测试等,后期可以作为质量保证手段

到这必须要输出一份可以运行的代码,通过运行调试确保所有工作的正确与合理

"use strict";

/*
模块化: http://www.ruanyifeng.com/blog/2012/10/javascript_module.html

es6 module: http://es6.ruanyifeng.com/#docs/module
CMD规范: https://github.com/seajs/seajs/issues/242
AMD规范: https://github.com/amdjs/amdjs-api/wiki/AMD

requirejs: http://requirejs.org/docs/whyamd.html
*/

;(function (global, factory,namespace) {
    if(typeof exports === 'object' && typeof module !== 'undefined'){
        module.exports = factory();
    }else if(typeof define === 'function' && define.amd){
        define(factory);
    }else{
        global[namespace] = factory();
    }
})(window, function(){
    /*
    emoji参考资料:

    https://github.com/maicong/Twitter-Emoji
    http://blog.getemoji.com/post/57054354336/which-browsers-support-emoji

    http://caniemoji.com/
    https://gist.github.com/mwunsch/4710561

    http://www.fileformat.info/info/unicode/char/1f436/index.htm

    http://www.ruanyifeng.com/blog/2017/04/emoji.html
    */

    /* 工具类 */
    var utils = {
        /*
        template = '<p>{{this.key2}}, <b>{{this.key1.key11}}</b></p>';
        data = {
            "key1": {
                "key11" : "value11"    
            },
            "key2" : "value2"
        };
        data = [data,data]
        */
        subs : function(template, data) {
            template = template || "";
            data = data || [""];
            var re = /{{((?:(?!}}).)+)}}/g, reExp = /(^( )?(var|if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
            var add = function(line, js) {
                js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
                    (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
                return add;
            }
            var match;
            while(match = re.exec(template)) {
                add(template.slice(cursor, match.index))(match[1], true);
                cursor = match.index + match[0].length;
            }
            add(template.substr(cursor, template.length - cursor));
            code += 'return r.join("");';

            data = isNaN(data.length) ? [data] : data;
            var html = "";
            for(var i = 0, length = data.length; i < length; i++) {
                html += new Function(code.replace(/[\r\t\n]/g, '')).apply(data[i]);
            }
            return html;    
        },

        /*
        emojiUnicode = "u1F600";
        */
        getEmoji : function(emojiUnicode){
            var emojiUnicode = "u1F600";
            var code = "0x" + emojiUnicode.replace("u","");
            var emoji = String.fromCodePoint(code);
            return emoji;
        },

        /*
        error = {
            "module" : "voip/emoji/voice/sdk"
            "code" : -1,
            "info" : "网络超时 timeout"
        };
        */
        errorReport : function(error){
            console.log(error);

            var api = "http://domain?data="
            var data = JSON.stringify(error);
            console.log(api + encodeURIComponent(data));
        }
    };

    var isSupport = (function(){
        return Math.random() > 0.5 ? true : false;
    })();

    /*emoji支持情况*/
    var supportList = {
        "windows" : {
            "Chrome41+" : true,  /* Chrome 41 开始支持 Emoji 显示(全平台) */
            "Firefox50+" : true,    /*Firefox 50 开始支持 emoji 支持 */
            "Chrome" : false
        },
        "Mac" : {
            "Chrome41+" : true,
            "Firefox50+" : true,
            "Chrome" : false
        },
        "Android" : true,   //小米有问题
        "iPhone" : true
    };    

    function getEmojiImage(emojiUnicode){
        return '<span class="emoji-1f600">[大笑]</span>';
    }

    function getEmojiName(emojiUnicode){
        return {
            "en" : "[grinning]",
            "cn" : "[大笑]"
        };
    }

    function getEmojiAuto(emojiUnicode){
        if(isSupport){
            return utils.getEmoji(emojiUnicode);
        }else{
            return getEmojiImage(emojiUnicode);
        }
    }

    return {
        utils : utils,
        getName : getEmojiName,

        // getEmojiAuto : getEmojiAuto,
        // getEmojiImage : getEmojiImage,


        /*
        type = "auto/image/emoji"
        */
        show : function(emojiUnicode,type){

            if(type && type!= "auto" && type!= "image" && type!= "emoji"){
                var error = {
                    "module" : "emoji",
                    "code" : 10001,
                    "info" : "wrong type,type only can be auto,image,emoji,empty-string"
                };
                utils.errorReport(error);
            }

            if(type == "emoji"){
                return utils.getEmoji(emojiUnicode);
            }else if(type == "image"){
                return getEmojiImage(emojiUnicode);                
            }else{
                return getEmojiAuto(emojiUnicode);
            }
        },
        init : function(config){
            /*
            config = {
                emojis : {
                    "u1F600":{
                        "en":"grinning",
                        "cn":"狂笑",
                        "position":"0px 0px"
                        "image" : "1f600.svg"
                    },
                },
                size : "20px",
                type : "auto/image/emoji"
            }
            */
        }
    }
}, "Emoji");

/*
Emoji.show("u1F600","auto");
Emoji.show("u1F600");
Emoji.getName("u1F600").en;
*/

至此,才开始进入真正的代码实现,但一路顺下来不难发现,其实大部分的工作已经做完了,剩下的就只是具体已经定义好输入输出方法的实现,不管谁来实现,都不会影响整体调用。

开发里有句话叫“依赖于接口,不依赖于实现”,在这儿,我想说,设计和流程大于实现,实现可以差一点,可以随时改进,但整体的解决方法、调用流程是应该第一时间既定的,定义好后就可以同步开始做文档、调用示例、测试验证等工作,同时又极大的降低了沟通成本。

同时我们也会发现,要完成这个既定,代码之外的综合能力要求其实是非常高的,对于软件运行环境、硬件、外部世界的现实、用户等都需要有一定的认知,要观察分析总结,要条理表达,要强于设计,当然还必须要能输出优秀的文档。经常开玩笑说,写代码真的不难,大家都可以学会,但写代码之前的这些一时半会还真的学不会。


PS:本文着重于工程师的工作流程与方法,选择性的忽略掉了组件设计与开发、相关的设计模式等技术性内容,模块化虽然专业但属于范式,只有第 5 步是专业的代码研发,前 4 步只要懂产品技术的常识就可以完成这样的设计,关键是方法论,方法对,都可以来做工程师。