如何从容应对复杂性

发表于 2年以前  | 总阅读数:282 次

‍软件的复杂性,是一个很泛的概念。 但是一直都是开发过程中的一个难题,本文旨在探讨如何去从容应对复杂性。

一 软件的熵增、构造定律

1 熵增定律

熵的概念最早起源于物理学,热力学第二定律(又称“熵增定律”),表明了在自然过程中,一个孤立的系统总是从最初的集中、有序的排列状态,趋向于分散、混乱和无序;当熵达到最大时,系统就会处于一种静寂状态。 软件系统亦是如此, 在软件系统的维护过程中。软件的生命力会从最初的集中、有序的排列状态,逐步趋向复杂、无序状态,直到软件不可维护而被迫下线或重构。

2 构造定律

自然界是如何应对这复杂性?

这在物理中被称为构造定律 (Constructal Law), 是由Adrian Bejan于1995提出的:

For a finite-size system to persist in time (to live), it must evolve in such a way that it provides easier access to the imposed currents that flow through it.

对于一个有限大小的持续活动的系统,它必须以这种方式发展演进:它提供了一种在自身元素之间更容易访问的流动方式。

这个定理在自然界中比比皆是,最典型的比如水循环系统,海水蒸发到大气,下雨时降落在地面,一部分渗入地面流入江河,一部分继续蒸发,不断循环。这种自发性质的设计反映了这一趋势:他们允许实体或事物更容易地流动 - 以最少的能量消耗到达最远的地方,就连街道和道路这些人为地构建物体,往往也是有排序的模式,以提供最大的灵活性。

二 如何应对软件系统的复杂性?

软件系统的复杂性往往是被低估的。复杂越高,开发人员会感到不安。对其的理解认知负荷代价就越高,我们就更不快乐。真正的挑战是在构建我们的系统时要保持其有序以及工程师的生产方式。

Ousterhout教授在《软件设计的哲学》书中提到:软件设计的最大目标,就是降低复杂度(complexity)。

就是设计符合业务的构造定律的演进方式,一种可以以最小的开发维护成本, 使业务更快更好的流动发展的方式。

三 软件复杂性来自哪里, 如何解决?

1 不确定性的来源

1、业务的不确定性

2、技术的不确定性

3、人员流动的不确定性

2 如何面对不确定性

面对外部的确定性,转化为内核的确定性。

面对外部的不确定性,找到稳定的内核基础。

专注问题域

当下互联网发展速度是迅猛的, 软件的形态也在不断的变化演进。面对未来的业务及变化,横向业务与纵向业务的发展都是不确定性的。

Robert C. Martin提到的BDUF,永远不要想着在开始就设计好了全部的事情(big design up front),一定要避免过度设计。除非能够十分确认的可预见变化, 业务边界,否则专注解决当前1-2年内业务变化设计, 讲好当下的用户故事,专注解决眼前的问题域。 面向不确定设计,增量敏捷开发。

确认稳定的系统内核

随着业务的变化、系统设计也要持续演进升级。没有一开始就完美的架构, 好的架构设计一定演化来的,不是一开始就设计出来的。

一个健康公司的成长,业务横向、纵向会发展的会越来越复杂,支持业务的系统也一定会越来越复杂。

系统演进过程中的成本,会受到最开始的设计、系统最初的内核影响的。面对外部业务的不确定性, 技术的不确定性,外部依赖的不确定性。一个稳定的内核应该尽量把外部的不确定性隔离。

  • 业务与技术的隔离

以业务为核心,分离业务复杂度和技术复杂度。- 内部系统与外部依赖的隔离

  • 系统中常变部分与不常变部分的隔离
  • 隔离复杂性(把复杂性的部分隔离在一个模块,尽量不与其他模块互动)

3 无序性

系统和代码像多个线团一样散落一地一样,混乱不堪,毫无头绪。

4 如何面对无序性

1、统一认知(秩序化)

2、系统清晰明了的结构(结构化)

3、业务开发流程化(标准化)

注:这里说的流程化并非指必须使用类似BPM的流程编排系统,

而是指对于一个需求,业务开发有一定的顺序, 有规划的先做一部分事情,开发哪一个模块再去做剩下的工作,是可以流程化的。

5 规模

业务规模的膨胀以及开发团队规模的膨胀,都会带来系统的复杂性提升。

6 如何面对规模膨胀带来的复杂性

1、业务隔离, 分而治之

2、专注产品核心竞争力的发展

3、场景分层

关键场景

投入更多的开发、测试资源、业务资源(比如单元测试覆盖率在90%以上)在关键场景。

普通场景

更快,更低成本、更少资源投入地完成普通场景的迭代

7 认知成本

是指开发人员需要多少知识才能完成一项任务。

在引入新的变化时,要考虑到带来的好处是否大于系统认知成本的提升,比如:之前提到的BPM流程编排引擎,如果对系统带来的好处不够多也是增加认知成本的一种。

不合适的设计模式也是增加认知成本的一种,前台同学吐槽的中台架构比较高的学习成本, 也是认知成本的一种。

8 如何降低认知成本

1、系统与现实业务更自然真实的映射,对业务抽象建模

软件工程师实际上只在做一件事情,即把现实中的问题搬到计算机上,通过信息化提升生产力。

2、代码的含义清晰,不模糊

3、代码的整洁度

4、系统的有序性, 架构清晰

5、避免过度设计

6、减少复杂、重复概念, 降低学习成本

7、谨慎引入会带来系统复杂性的变化

四 应对复杂性的利器

1 领域驱动设计——DDD

DDD是把业务模型翻译成系统架构设计的一种方式, 领域模型是对业务模型的抽象。

不是所有的业务服务都合适做DDD架构,DDD合适产品化,可持续迭代,业务逻辑足够复杂的业务系统,小规模的系统与简单业务不适合使用,毕竟相比较于MVC架构,认知成本和开发成本会大不少。但是DDD里面的一些战略思想我认为还是较为通用的。

对通用语言的提炼和推广

清晰语言认知, 比如之前在详情装修系统中:

ItemTemplate : 表示当前具体的装修页面

ItemDescTemplate、Template,两个都能表示模板概概念

刚开始接触这块的时候比较难理解这一块逻辑,之后在负责设计详情编辑器大融合这个项目时第一件事就是团队内先重新统一认知。

  • 装修页面统一使用 —— Page概念
  • 模板统一使用 —— Template概念

不将模板和页面的概念糅杂在一起,含糊不清,避免重复和混乱的概念定义。

贫血模型和充血模型

1)贫血模型

贫血模型的基本特征是:它第一眼看起来还真像这么回事儿。项目中有许多对象,它们的命名都是根据领域模型来的。然而当你真正检视这些对象的行为时,会发现它们基本上没有任何行为,仅仅是一堆getter/setter方法。

这些贫血对象在设计之初就被定义为只能包含数据,不能加入领域逻辑;所有的业务逻辑是放在所谓的业务层(xxxService, xxxManager对象中),需要使用这些模型来传递数据。

@Data
public class Person {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 生日
     */
    private Date birthday;

    /**
     * 当前状态
     */
    private Stauts stauts;
}

public class PersonServiceImpl implements PersonService {

    public void sleep(Person person) {
        person.setStauts(SleepStatus.get());
    }

    public void setAgeByBirth(Person person) {
        Date birthday = person.getBirthday();
        if (currentDate.before(birthday)) {
            throw new IllegalArgumentException("The birthday is before Now,It's unbelievable");
        }
        int yearNow = cal.get(Calendar.YEAR);
        int dayBirth = bir.get(Calendar.DAY_OF_MONTH);
        /*大概计算, 忽略月份等,年龄是当前年减去出生年*/
        int age = yearNow - yearBirth;
        person.setAge(age);
    }
}
}
public class WorkServiceImpl implements WorkService{

    public void code(Person person) {
        person.setStauts(CodeStatus.get());
    }

}

这一段代码就是贫血对象的处理过程,Person类, 通过PersonService、WorkingService去控制Person的行为,第一眼看起来像是没什么问题,但是真正去思考整个流程。WorkingService, PersonService到底是什么样的存在?与真实世界逻辑相比, 过于抽象。基于贫血模型的传统开发模式,将数据与业务逻辑分离,违反了 OOP 的封装特性,实际上是一种面向过程的编程风格。但是,现在几乎所有的 Web 项目,都是基于这种贫血模型的开发模式,甚至连 Java Spring 框架的官方 demo,都是按照这种开发模式来编写的。

面向过程编程风格有种种弊端,比如,数据和操作分离之后,数据本身的操作就不受限制了。任何代码都可以随意修改数据。

2)充血模型

充血模型是一种有行为的模型,模型中状态的改变只能通过模型上的行为来触发,同时所有的约束及业务逻辑都收敛在模型上。


@Data
public class Person extends Entity {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 生日
     */
    private Date birthday;

    /**
     * 当前状态
     */
    private Stauts stauts;

    public void code() {
        this.setStauts(CodeStatus.get());
    }

    public void sleep() {
        this.setStauts(SleepStatus.get());
    }

    public void setAgeByBirth() {
        Date birthday = this.getBirthday();
        Calendar currentDate = Calendar.getInstance();
        if (currentDate.before(birthday)) {
            throw new IllegalArgumentException("The birthday is before Now,It's unbelievable");
        }
        int yearNow = currentDate.get(Calendar.YEAR);
        int yearBirth = birthday.getYear();
        /*粗略计算, 忽略月份等,年龄是当前年减去出生年*/
        int age = yearNow - yearBirth;
        this.setAge(age);
    }

}

3)贫血模型和充血模型的区别

/**
 * 贫血模型
 */
public class Client {

    @Resource
    private PersonService personService;

    @Resource
    private WorkService workService;

    public void test() {
        Person person = new Person();
        personService.setAgeByBirth(person);
        workService.code(person);
        personService.sleep(person);
    }
}


/**
 * 充血模型
 */
public class Client {

    public void test() {
        Person person = new Person();
        person.setAgeByBirth();
        person.code();
        person.sleep();
    }
}

上面两段代码很明显第二段的认知成本更低, 这在满是Service,Manage 的系统下更为明显,Person的行为交由自己去管理, 而不是交给各种Service去管理。

贫血模型是事务脚本模式

贫血模型相对简单,模型上只有数据没有行为,业务逻辑由xxxService、xxxManger等类来承载,相对来说比较直接,针对简单的业务,贫血模型可以快速的完成交付,但后期的维护成本比较高,很容易变成我们所说的面条代码。

充血模型是领域模型模式

充血模型的实现相对比较复杂,但所有逻辑都由各自的类来负责,职责比较清晰,方便后期的迭代与维护。

面向对象设计主张将数据和行为绑定在一起也就是充血模型,而贫血领域模型则更像是一种面向过程设计,很多人认为这些贫血领域对象是真正的对象,从而彻底误解了面向对象设计的涵义。

Martin Fowler 曾经和 Eric Evans 聊天谈到它时,都觉得这个模型似乎越来越流行了。作为领域模型的推广者,他们觉得这不是一件好事,极力反对这种做法。

贫血领域模型的根本问题是,它引入了领域模型设计的所有成本,却没有带来任何好处。最主要的成本是将对象映射到数据库中,从而产生了一个O/R(对象关系)映射层。

只有当你充分使用了面向对象设计来组织复杂的业务逻辑后,这一成本才能够被抵消。如果将所有行为都写入到Service对象,那最终你会得到一组事务处理脚本,从而错过了领域模型带来的好处。而且当业务足够复杂时, 你将会得到一堆爆炸的事务处理脚本。

对业务的理解和抽象

限定业务边界,对业务进行与现实更自然的理解和抽象,数据模型与业务模型隔离,把业务映射成为领域模型沉淀在系统中。

结构与防腐层

User Interfaces

负责对外交互, 提供对外远程接口

application

应用程序执行其任务所需的代码。

它协调域层对象以执行实际任务。

该层适用于跨事务、安全检查和高级日志记录。

domain

负责表达业务概念。

对业务的分解,抽象,建模 。

业务逻辑、程序的核心。

防腐层接口放在这里。

infrastucture

为其他层提供通用的技术能力。如repository的implementation(ibatis,hibernate, nosql),中间件服务等anti-corruption layer的implementation 防腐层实现放在这里。

防腐层的作用:

封装三方服务。

隔离内部系统对外部的依赖。

让隐性概念显性化

文档与注释可能会失去实时性(文档、注释没有人持续维护),但是线上生产代码是业务逻辑最真实的展现,减少代码中模糊的地方,让业务逻辑显性化体现出来,提升代码清晰度。


if (itemDO != null && MapUtils.isNotEmpty(itemDO.getFeatures()) && itemDO.getFeatures().containsKey(ITEM_PC_DESCRIPTION_PUSH)) {
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
} else {
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_WL_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_WL_PUSH, "" + content.hashCode());
}

比如这一段代码就把判断里的业务逻辑隐藏了起来,这段代码其实的业务逻辑是这样, 判断商品是否有PC装修内容。如果有做一些操作, 如果没有做一些操作,将hasPCContent 这个逻辑表现出来, 一眼就能看出来大概的业务逻辑,让业务逻辑显现化,能让代码更清晰。可以改写成这样:

boolean hasPCContent = itemDO != null && MapUtils.isNotEmpty(itemDO.getFeatures()) && itemDO.getFeatures().containsKey(ITEM_PC_DESCRIPTION_PUSH);
if (hasPCContent) {
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
} else {
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_PC_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_WL_TEMPLATEID, "" + templateId);
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_PC_PUSH, "" + pcContent.hashCode());
   itemUpdateBO.getFeatures().put(ItemTemplateConstant.FEATURE_TSP_SELL_WL_PUSH, "" + content.hashCode());
}

2 简单设计原则——《Clean Code》

1、保持系统最大可测试

只要系统可测试并且越丰富的单元测试越会导向保持类短小且目的单一的设计方案,遵循单一职责的类,测试起来比较简单。

遵循有关编写测试并持续运行测试的简单、明确规则,系统就会更贴近OO低偶尔度,高内聚度的目标。编写测试越多,就越会遵循DIP之类的规则,编写最大可测试可改进并走向更好的系统设计。

2、避免重复

重复是拥有良好设计系统的大敌。它代表着额外的工作、额外的风险和额外且不必要的复杂度。除了雷同的代码,功能类似的方法也可以进行包装减少重复,“小规模复用”可大量降低系统复杂性。要想实现大规模复用,必须理解如何实现小规模复用。

共性的抽取也会使代码更好的符合单一职责原则。

3、更清晰的表达开发者的意图

软件项目的主要成本在于长期维护,当系统变得越来越复杂,开发者就需要越来越多的时间来理解他,而且也极有可能误解。

所以作者需要将代码写的更清晰:选用好名称、保持函数和类的短小、采用标准命名法、标准的设计模式名,编写良好的单元测试。用心是最珍贵的资源。

4、尽可能减少类和方法

如果过度使用以上原则,为了保持类的函数短小,我们可能会造出太多细小的类和方法。所以这条规则也主张函数和类的数量要少。

如应当为每个类创建接口、字段和行为必须切分到数据类和行为类中。应该抵制这类教条,采用更实用的手段。目标是在保持函数和类短小的同时,保持系统的短小精悍。不过这是优先级最低的一条。更重要的是测试,消除重复和清晰表达。

五 最后

总而言之,做业务开发其实一点也不简单,面对不确定性的问题域,复杂的业务变化,

如何更好的理解和抽象业务,如何更优雅的应对复杂性,一直都是软件开发的一个难题。

在对抗软件熵增,寻找对抗软件复杂性,符合业务的构造定律的演进方式,我们一直都在路上。

参考

[1] 《Domain-Driven Design》 :https://book.douban.com/subject/1629512/

[2] 《Implementing Domain-Driven Design》 :https://book.douban.com/subject/25844633/

[3] 《Clean Code》:https://book.douban.com/subject/4199741/

[4] 《A Philosophy of Software Design》 :https://book.douban.com/subject/30218046/

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/8YD9sqTuZGJEpVZPYY-aBQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237227次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录