小程序中的大道理--综述

news2024/12/23 6:53:46

前言

以下将用一个小程序来探讨一些大道理, 这些大道理包括可扩展性, 抽象与封装, 可维护性, 健壮性, 团队合作, 工具的利用, 可测试性, 自顶向下, 分而治之, 分层, 可读性, 模块化, 松耦合, MVC, 领域模型, 甚至对称性, 香农的信息论等等.

为什么不用大程序来说大道理呢?

因为大程序太大了, 代码一端上来, 读者就晕菜了, 看不过来甚至压根不想去看, 这样说理就很抽象了, 效果反而不好.

小程序中也能说出大道理来吗?

我们有句话, 叫"以小见大", 我们又常常有种说法, 叫:

麻雀虽小, 五毒俱全. (咦? 好像应该是五脏俱全…总之你明白我的意思就好了. )

所以呢, 小程序也是可以来说大道理的, 而且小程序又有短小的特点, 大家看得也没那么累, 也很快能看懂. 毕竟那种代码, 叫什么来着, “意大利面条式的代码”, 大家在实际的开发中, 已经见得太多了.

意大利面(spaghetti), 翻译过来好像叫"通心粉", 非常长的一条条, 彼此缠缠绕绕的, 所以"意大利面条式的代码"就是又长又绕, 让人非常头痛的那种代码.

按我们的习惯, 也许叫它"裹脚布式的代码"大家觉得更熟悉, 更形象一点, 也正好符合"又长又臭"的特点.

啊, 说"又长又臭"可能有点刻薄了, 毕竟大家都可能写过这样的代码(本人就写过好多), 即便现在不会再写这样的代码, 想当年应该也是写过的, 除非你从一开始觉悟通天, 那我就无话可说了.

这种代码我们在工作中见得太多了, 所以这里就不再弄出来考验大家的毅力了, 闲话少提, 让我们看个简单的例子.

我们的例子

就是要打印出如下的一个三角形图案:

  *
 ***
*****

当然了, 这只是以三行为一个示例, 我们的程序应该接受任意的正整数, 比如, 给一个 5, 就要能打出 5 行的类似的三角形来. 让我们来看看如何写出这样一个程序, 并在这个过程中借此兜售我们的大道理.

玩具式的代码

我知道很多"数学帝"可能一眼就被图案中的规律吸引过去了, 他们很快就指出星号是等差数列, 然后很快就弄出了计算每行前面要缩进多少个空格的公式, 然后呢, 一层循环, 二层循环, blablablah…然后最里面几条优雅而性感的 print 语句, 搞掂!一种智商上的优越感油然而生, 接着他们可能就要问:

这么简单的东西, 你也好意思拿出来讲?

下面是这样的一个代码, 能够完全实现以上要求(只演示了 3 行的情况):

public static void main(String[] args) {
    int i = 3;
    for (int j = 0; j < i; j++) {
        for (int k = 0; k < i - j; k++) {
            System.out.print(" ");
        }
        for (int z = 0; z < 2*j+1; z++) {
            System.out.print("*");
        }
        System.out.println();
    }
}

这里用的是 java 语言来演示, 包括以下的. 我相信像 java 这样烂大街的语言, 即使你没这个背景看懂也不是难事, 在代码中也不会用到什么高深的特性. (这一点皆因我的能力有限所导致, 而不是想装逼的意愿所能决定的~)

怎么说呢, 我们不要以上那种"玩具式"的代码(toy program), 我们要的是生产级(production, 生产环境)的代码.

生产级的代码

让我们来看看如何写出这样的代码.

可扩展性(Extensibility)

首先呢, print 语句是绝对要避免的. 你要明白, print 语句写得太死, 而需求是不断在变化的, 有句话是怎么说的?

唯一不变的就是变化本身.

客户今天跟你说的是要 print 这个图案, 你要是按着客户怎么说, 你就怎么做, 你可就惨了.

客户哪一天突然又会说, 再加点特性, 要能输出到文件;哪一天又说, 再加点 web service, 能供其它程序调用.

让我们多留点心眼, 代码如下:

public void printPattern(int lineCount) {
    String pattern = getPattern(lineCount);
    System.out.print(pattern);
}

我们先借助 getPattern 方法拿到要打印的内容, 这样, 如果哪天要输出到文件, 哪天要供 web service 调用, 我们都可以把这个 getPattern 方法提供出去.

我们只要多抽象出那么一层来, 就会给我们带来很多方便.

抽象与封装(Abstraction & Encapsulation)

抽象与封装同时也是很多其它特性的基础, 在后面我们还会不断说到这一主题.

getPattern 就是一个抽象, 是对一系列动作的一个封装.

可能有人会比较教条地认为抽象与封装只能在类层次中进行, 这常常导致在类的内部缺少必要的抽象层次, 常常是一大件事情在一个方法里完成, 方法巨大巨长无比, 这样的所谓面向对象编程不过是虚有其表, 其模块性甚至还比不上那些用面向过程语言写就的代码.

printPattern 层面, 我们不需要知道 getPattern 的细节, 我们只需要传入所需参数及定义好需要的返回值即可.

大道理: 定义好输入与输出, 描述清楚想要做的事, 先不用去管细节.

然后呢, 我们是不是需要手动去把这个方法写出来呢?

利用好你的工具(Tools)

你不用手动去做这些, 以 eclipse 为例, 只要把光标定位到错误的地方(可以按Ctrl+“.”(点)快速定位), 然后按下"Ctrl+1", 然后选择"Create Method"即可:

eclipse create method

工具将根据传入参数及返回值自动为我们生成方法, 结果如下:

eclipse create method

只要输入与输出定义清楚了, 工具就能自动帮我们生成方法定义, 这里默认它是 private 的, 我们可以把它改成 public.

这里说的是 Eclipse 这个 IDE, 其它的我相信也会有类似的功能. 如果你偏好轻量级的文本编辑器, 那我就不敢说也一定有这些功能了.

利用好任务标识(Task Tags)

我们可以看到, 生成的代码里有个 TODO, 显示出了特别的颜色, 这是个任务标识.

类似的标识还有 FIXME, XXX, 甚至你还可以自定义标识.

打开 eclipse 的菜单-- windows–preferences, 在过滤框中输入"task tag":

eclipse task tag

这些有什么用呢? 我们可以看下, 在编辑器的左右侧, 都有显著的标志提示有个任务标识存在;在 Markers 视图里, 有列举出这些标识:

eclipse todo task tag

在代码质量分析工具 sonar 中, 它也会追踪这些标识. 下图是我在 sonar.oschina.net 上的一个项目的截图:

sonar todo tag

这些有什么用呢? 我们在写代码中, 写到一半, 很可能被某些难题卡住了, 为了不中断正常的流程, 我们先用个 TODO 来标识, 然后就可以继续地把一些简单的问题先处理完, 再回过头来对付这些.

又或者像现在这样, 我们生成了出来了这个方法, 工具为我们自动加了入"TODO"的标识, 毕竟方法的主体还没有, 可不巧的是, 现在到了下班时间了, 然后呢, 我们就可以存盘并提交到 svn 或者 git 上去了. 有人可能要说: "啊? 不是吧, 你的代码都没写完你怎么就提交了? "

没关系, 我们已经标识好了 TODO, 所以它会提醒我们还有工作是没做完的. 另外我们为何如此着急提交呢? 因为我们并不是在单打独斗:

团队合作(Teamwork)

我们前面说了, 我们可能还要做输出图案到文件的需求, 很可能你有个同事哥们, 他就正做着这个模块, 而他现在呢, 就在等着你这个 getPattern 方法. 你提交了, 他就可以继续写他的代码了:

package org.jcc.core.demo;

public class PatternFile {
    
    private Pattern pattern;

    public PatternFile(Pattern pattern) {
        this.pattern = pattern;
    }
    
    public void generatePatternFile(int lineCount) {
        String content = pattern.getPattern(lineCount);
        saveInFile(content);
    }

    private void saveInFile(String content) {
        // TODO Auto-generated method stub
        //System.out.println(content);
    }
}

可以看到, 他的类依赖你的类, 在他的方法 generatePatternFile 里还调用了 getPattern 方法, 你没实现, 那又怎样呢? 接口好了就行了!

面向接口编程(Interface)

有人可能比较死板, 比较教条主义, 以为呢, 说到接口就一定要弄个 interface, 其实呢, 我们这个方法 getPattern 就是一个承诺, 一个约定, 一个协议, 也是一个广义上的接口.

有人可能要问, 你方法细节还没有实现, 他怎么测试? 别担心, 办法会有的:

利用 Mockito 来测试

代码如下:

@Test
public void testGeneratePatternFile() {
    // 用mockito来模拟接口的行为, 为此我们手动构建一个三行的图案
    Pattern pattern = Mockito.mock(Pattern.class);
    String mockContent = "  *" + System.lineSeparator() 
                       + " ***" + System.lineSeparator() 
                       + "*****" + System.lineSeparator();
    
    // 当调用getPattern方法时, 就返回这里定义好的内容. 
    Mockito.when(pattern.getPattern(3)).thenReturn(mockContent);
    
    // 测试generatePatternFile方法, 在它的里面将会调用getPattern方法
    PatternFile pf = new PatternFile(pattern);
    pf.generatePatternFile(3);
    
    // TODO 断言文件存在并且文件中的内容与mockContent一致
    // assert that file is exists and content in file is equals the mock content
}

以上我们用一个 mock 对象以及 when, thenReturn 来主动模拟一个尚未实现的方法.

你也许对 Mock 之类的技术还不太了解, 但这些词表达的意思我想大家都不难明白. Mock 的更详细介绍请自行百度之.

借助 Mockito, 这个哥们就可以这样写好他的代码, 并完成他的测试了, 然后可以提交他的代码, 宣布工作完成, 接着他就可以飞到马尔代夫去度假去了.

可以看到, 尽管我们的功能八字还没一撇, 可只要我们坚持面向接口编程, 时时想着团队合作, 经常提交已经写好的代码, 特别是公共接口方面的代码, 我们的同事就能及时推进他们的工作, 甚至比我们还早完成, 这都是有可能的, 都是正常的, 也是我们应该追求的.

而利用好抽象及封装, 我们还能得到好几个好处:

可测试性(Testability)

通过以上举例, 可以看到, 我们可以手动构建一个图案, 并交给程序去判断(注: 为了简短起见, 代码中省略了具体的 assert 细节). 而如果是开头那样直接就打印了呢? 你根本没法让程序去判断, 只能通过人眼去观察输出, 这样就给 自动化的测试(Automatic Test) 带来了困难.

可重用性(Reusability)

getPattern 被抽象出来之后, 可以看到, 不但可以在 printPattern 方法里使用, 也可以在 generatePatternFile 方法里使用. 而如果按开头那样呢? 你没法复用, 你还是不得不重构;又或者你可能只是简单地把代码复制一遍了, 再作些改动.

当然, 现在这个程序很小, 全部拷贝一遍好像也很快, 但如果是很大的程序呢? 又或者我们又要拓展到可供 web service 调用, 难道就这样拷贝下去? 哪一天程序要做些小调整, 难道又要一一去修改吗?

不要重复(DRY: Don’t Repeat Yourself)

管理重复性一直都是程序开发中的重大关切, 在目前这个小程序里, 这一问题还不是那么迫切, 这个在此就不作详述, 以后会另写一些文章来做些介绍.

好了, 说了一大通, 绕了一大圈, 测试也测了, 同事也度假去了, 我们也要赶紧我们的工作. 那么接下来是不是赶紧写那些实现呢? 不!

我们已经介绍了不少的"ility"结尾的单词, 接下来还要说到!我有点担心大家说我"zhuangbility", 有句话说: “Don’t zhuangbility, zhuangbility leads to leipility”(莫装逼, 装逼遭雷劈), 没办法, 为了阐述这些大道理, 我也只好冒着被 leipility 的危险.

可维护性(Maintainability)

你首先把注释写好:

怎么说呢? 现在 IT 工作强度很大, 过劳死是不稀奇的事, 写着写着说不定哪天人就挂了. 一个人挂了不要紧, 工作可不能挂!(不是在说笑话, 貌似有些公司或老板表现出来的态度就是这样的~)

别人要能顺利接手你的活, 这是关键.

其实没必要说"挂了"这些不吉利的话, 也可能是有人要生了, 比如你老婆要生了, 你也休产假去了, 你写到一半, 老板把你的工作转交给你的同事.

试想, 要是一点注释都没有, 你的同事接手起来就很困难, 他要加班加点才能早点弄清你的代码的意图. 所以呢, 不要害了你的同事!把代码的可维护性做好, 大家的健康也才有更好的可维护性!

代码如下:

/**
 * 获取指定行数的图案, 比如3行时: 
 *   *
 *  ***
 * *****
 *  
 * @param lineCount 指定的行数
 * @return 图案的字符串表示, 包括换行符在内
 */
public String getPattern(int lineCount) {
    // TODO Auto-generated method stub
    return null;
}

其实, 良好的命名同样也是可维护性的关键, 比如上面的 getPattern, lineCount, 而不是像最前面那个示例中的 i, j, k, z 等乱七八糟的名字.

另外, 丰富的抽象层次也是如此, 这点我们后续还会不断提及.

好了, 注释也写完了, 然后呢, 现在该轮到写那个该死的等差数列了吧? 不!

健壮性(Robustness)

Robustness 又常常音译成鲁棒性.

作者在大学时读的是自动化专业, 在那些自动控制理论里, 老出现什么鲁棒性, 看了让人犯晕, 不如直接叫健壮性.

我倒是想起了小时候老爸常买给我喝的 Robust(即乐百氏, 与娃哈哈类似的饮品), 味道是不错, 不过喝完身体挺没见得健壮到哪去, 也许喝得还不够多~

我们先要把判断做好, 输入负数或者输入的数字太大了, 你要拒绝它们, 同时在注释中也作出说明:

/**
 * 获取指定行数的图案, 比如3行时: 
 *   *
 *  ***
 * *****
 *  
 * @param lineCount 指定的行数, 1-20之间
 * @return 图案的字符串表示, 包括换行符在内
 */
public String getPattern(int lineCount) {
    if (lineCount < 1) {
        throw new IllegalArgumentException("行数不能小于1!");
    }
    if (lineCount > 20) {
        throw new IllegalArgumentException("行数不能大于20!");
    }
    
    // TODO 
    return null;
}

我知道我在这里说这些, 有些人可能已经不耐烦了, 他们想着的是写那些有技巧的代码, 那些有挑战性的部分, 那些 tricky 的部分, 那些能体现出他们智商上的优越感的部分.

有个词是怎么说的, “rocket science”(火箭科学, 喻指那些高精尖的技术), 特别的有些刚毕业的心气很高的学生, 满脑子想的可能就是这些. 可是呢, 类似情况不是没有, 但通常是很少的:

骚年, 不是我在打击你, 你也许真的想多了. 工作上, 我们多数时候处理的都是一些细节的问题, 一些琐碎的事情, 一些按部就班的样板式的代码, 需要的不是多高的智商, 多么 tricky 的技巧, 要是是耐心, 细致, 严谨, 一丝不苟.

为何一开始就要把这些做好呢? 因为到了后面, 你就没时间去做了. 这一点你一定要相信我, 以下引自 wiki 的"90-90法则":

前 90% 的代码要花费你 90% 的开发时间, 剩余的 10% 的代码要花费你另一个 90% 的开发时间.

The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.

–Tom Cargill, 贝尔实验室

而最后如果因为时间紧急, 就这样没保护就上了生产环境, 一旦出了问题, 你会花更多的时间去收拾这些烂摊子, 而最终你还是不得不将这些补上.

有一个"墨菲定律"(Murphy’s Law)大意是这么说的:

有可能出错的的东西一定会出错.

现在不擦屁股, 后面还有得擦. 你省掉了纸尿裤, 你的程序就裸奔了, 你就等着洗更多的外套.

我们也常说: "该来的一定会来. "如果用电影<<无间道>>里的话来说呢, 那就是:

“出来混, 迟早要还的”. (哇塞, 说得太精彩了. 这些编导或者剧作家不去写教科书太可惜了. )

所以呢, 不要有侥幸的心理, 把程序从一开始就写健壮才是正道.

小结

说了半天, 我们甚至连一行核心代码都没写, 不过, 文章至此倒是要先做一个阶段了结了. 我们说写代码有个原则, 那就是方法不能太长, 最好一个屏幕就能显示完, 否则看起来就很累了;自然的, 文章也不能写得太长, 否则写起来, 读起来都很累人.

所以呢, 虽然一开始那里提了好多的道理, 本来也是想一扒到底的, 但扒到一半发现已经很长了, 所以上半身扒完, 就此腰斩, 下半身留待后面继续扒, 下半身更精彩, 我们下回再见.

下一篇, 见 小程序中的大道理之二 .

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1245913.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python写文件

output_file open(E:/XD_transfer/代码/CNN_new/try.csv, w) output_file.write(Sample, \n) for j in range(5):output_file.write(str(j) \n)

1-docker安装和配置、虚拟化、配置国内源、镜像操作、容器基本操作(run运行容器、-v目录映射、-p端口映射、容器其他操作)

1 docker和虚拟化 2 docker安装和配置 2.0 docker 中的一些概念 2.1 配置镜像加速器&#xff08;国内源&#xff09; 3 镜像操作 4 容器操作 4.1 容器基本操作 4.2 run运行容器 4.3 -v目录映射 4.4 -p端口映射 4.5 容器其他操作 1 docker和虚拟化 ## 什么是虚拟化在计算机中&…

数十亿美元商机!英国数字基础设施公司Equinix与法国量子计算公司Alice Bob 合作

​&#xff08;图片来源&#xff1a;网络&#xff09; 近日&#xff0c;全球数字基础设施公司Equinix宣布与全球领先的法国量子计算公司Alice & Bob合作&#xff0c;旨在共同开发市场上最为可靠的量子处理器之一。此次合作将使Equinix公司的客户通过使用Equinix Metal和Eq…

StarRocks Evolution:One Data,All Analytics

在 11 月 17 日举行的 StarRocks Summit 2023上&#xff0c;StarRocks TSC Member、镜舟科技 CTO 张友东详细介绍了 StarRocks 社区的发展情况&#xff0c;并全面解析了 StarRocks 的核心技术与未来规划&#xff1b;我们特意将他的精彩演讲整理出来&#xff0c;以帮助大家更深入…

合封芯片未来趋势如何?合封优势能否体现?

芯片已经成为现代电子设备的核心组件。为了提高系统的性能、稳定性和功耗效率&#xff0c;一种先进的芯片封装技术——合封芯片应运而生。 合封芯片作为一种先进的芯片封装技术&#xff0c;合封芯片是一种将多个芯片&#xff08;多样选择&#xff09;或不同的功能的电子元器件…

DevExpress中文教程 - 如何在macOS和Linux (CTP)上创建、修改报表(下)

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 DevExpress Reports — 跨平台报表组件&#x…

java 手机商城免费搭建+电商源码+小程序+三级分销+SAAS云平台

【SAAS云平台】打造全行业全渠道全场景的SaaS产品&#xff0c;为店铺经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营…

[kingbase锁等待问题分析]

参考文章:https://www.modb.pro/db/70021 概述 为了确保复杂的事务可以安全地同时运行&#xff0c;kingbase&#xff08;PostgreSQL&#xff09;提供了各种级别的锁来控制对各种数据对象的并发访问&#xff0c;使得对数据库关键部分的更改序列化。事务并发运行&#xff0c;直到…

Vatee万腾的科技冒险:vatee创新力量的前沿发现

在当今飞速发展的科技潮流中&#xff0c;Vatee万腾以其独特的创新力量成为前沿的引领者。这场科技冒险不仅仅是技术的迭代&#xff0c;更是一次前所未有的前沿发现之旅&#xff0c;让我们一同深入探索Vatee万腾的科技冒险&#xff0c;感受vatee创新力量的前沿奇迹。 Vatee万腾将…

基于opencv+ImageAI+tensorflow的智能动漫人物识别系统——深度学习算法应用(含python、JS、模型源码)+数据集(一)

目录 前言总体设计系统整体结构图系统流程图 运行环境爬虫1.安装Anaconda2.安装Python3.63.更换pip源4.安装Python包5.下载phantomjs 模型训练1.安装依赖2.安装lmageAl 实际应用1.前端2.安装Flask3.安装Nginx 相关其它博客工程源代码下载其它资料下载 前言 本项目通过爬虫技术…

云原生技术演进之路-(云技术如何一步步演进的,云原生解决了什么问题?)

云技术如何一步步演进的&#xff1f; 云原生解决了什么问题&#xff1f; 物理设备 电脑刚被发明的时候&#xff0c;还没有网络&#xff0c;每个电脑&#xff08;PC&#xff09;&#xff0c;就是一个单机。 这台单机&#xff0c;包括CPU、内存、硬盘、显卡等硬件。用户在单机…

vue 通过ref调用router-view子组件的方法

由于用的vue2.7版本&#xff0c;但用了vue3 setup的语法&#xff1b; 注意&#xff1a;是vue2的template结构&#xff0c;vue3的setup语法&#xff1b;非这种情况需要举一反三。 处理方案&#xff1a; 1、对router-view加上ref template修改 直接对router-view加上ref&#x…

Linux(6):文件与文件系统的压缩,打包与备份

压缩文件的用途与技术 由于 1 byte 8 bits &#xff0c;所以每个byte当中会有8个空格&#xff0c;而每个空格可以是0,1。 其实文件里面有相当多的『空间』存在&#xff0c;并不是完全填满的&#xff0c;而『压缩』的技术就是将这些『空间』填满&#xff0c;以让整个文件占用…

AI“胡说八道”?怎么解?

原创 | 文 BFT机器人 01 引言 近年来&#xff0c;人工智能产业迅猛发展&#xff0c;大型语言模型GPT-4发展势头强劲&#xff0c;OpenAI推出ChatGPT、微软推出Bing、马斯克推出“最好的聊天机器人Grok”……科技巨头纷纷入局AI领域&#xff0c;引入人工智能作为办公工具的行业…

APP软件外包开发流程

外包APP软件项目的开发流程可以分为以下几个主要阶段&#xff0c;在整个流程中&#xff0c;沟通和合作是至关重要的&#xff0c;确保开发团队和客户之间有良好的沟通渠道&#xff0c;及时解决问题&#xff0c;保证项目按时交付。北京木奇移动技术有限公司&#xff0c;专业的软件…

unigui同页面内重定向跳转,企业微信内部应用开发获取用户code例子

procedure TMainForm.UniFormCreate(Sender: TObject); varurl: string;code: string; begin //如果没有code值&#xff0c;将进行重定向if UniApplication.Parameters.Values[code] thenbeginurl :https://open.weixin.qq.com/connect/oauth2/authorize?appid你们的企业ID&…

Word怎么看字数?简单教程分享!

“我在写文章时&#xff0c;总是想看看写了多少字。但是我发现我的Word无法看到字数。在Word中应该怎么查看字数呢&#xff1f;请帮帮我&#xff01;” Word是一个广泛使用的文档编辑工具。在我们编辑文章时&#xff0c;如果想查看写了多少字&#xff0c;也是可以轻松完成的。 …

Whatweb简单使用

目录 简介 安装 debian/ubtuntu redhat/centos 特性 使用 常用参数如下&#xff1a; whatweb -v whatweb --version whatweb -i 1.txt whatweb -v www.baidu.com 扫描等级 whatweb -a 4 www.baidu.com 扫描网段 whatweb --no-errors -t 255 192.168.71.0/24 导出…

Android : AlertDialog对话框、单选、多选、适配器-简单应用

示例图&#xff1a; 1 &#xff1a;创建 AlertDialog.Builder 对象&#xff1b; 2 &#xff1a;调用 setIcon() 设置图标&#xff0c; setTitle() 或 setCustomTitle() 设置标题&#xff1b; 3 &#xff1a;设置对话框的内容&#xff1a; setMessage() 还有其他方法来指定显示…

windows11上安装WSL

Windows电脑上要配置linux&#xff08;这里指ubuntu&#xff09;开发环境&#xff0c;主要有三种方式&#xff1a; 1&#xff09;在windows上装个虚拟机&#xff08;比如vmware&#xff09;。缺点是vmware加载ubuntu后系统会变慢很多&#xff0c;而且需要通过samba来实现window…