面试冷知识,Redhat红帽/CentOS服务器的Linux内核实时系统

thbcm阅读(205)

本文分享了程序员面试关于 Linux 内核实时系统的冷知识。希望能让大家增长一点知识。

背景知识

Linux的调度策略包括SCHED_FIFO and SCHED_RR 和SCHED_OTHER

  • SCHED_FIFO(Round-robin线程调度策略)和SCHED_RR是“实时”策略。实现POSIX标准规定的固定优先级实时调度(fixed-priority real-time scheduling)。按照这些策略的任务会抢占其他所有任务。因为是抢占,如果它们不释放CPU,所以很容易使得某些低级别的任务得不到执行。
  • SCHED_FIFOSCHED_RR 之间的区别在于:在相同优先级的任务中,SCHED_RR 执行 round-robin 分配,每个任务都得到相同的cpu时间片策略。而SCHED_FIFO需要任务主动放弃cpu时间片。
  • SCHED_OTHER是常见的循环式分时调度策略,该调度策略根据系统中运行的其他任务为某个时间的调度任务。

SCHED_RR和SCHED_FIFO的问题:

红帽企业版Linux实时版中的两种实时调度策略具有一个主要特征:直到被更高优先级的线程抢占或直到它们“等待”(sleep/休眠或执行I/O)线程会一直运行。如果是SCHED_RR,在SCHED_RR优先级相同的线程中,操作系统可能会抢占一个线程,让另一个线程得以执行。

POSIX规范没有规定允许低优先级线程获得任何CPU时间的策略。

实时线程的这种特性意味着编写一个霸占100%的CPU的应用程序非常容易。乍一看,好像榨干了服务器是个不错的想法,但实际上,它引起了操作系统的许多问题。操作系统负责管理系统范围的资源和按CPU的资源,并且必须定期检查描述这些资源的数据结构,并对其执行内部管理活动。如果内核被SCHED_FIFO线程垄断,则它无法执行内务处理任务,最终整个系统将变得不稳定,从而可能导致崩溃。

中断处理程序以具有SCHED_FIFO优先级的线程(默认值:50)运行。具有或策略高于中断处理程序线程的cpuxiao hao x线程可能会阻止中断处理程序运行,并导致程序等待那些由中断发出的数据,从而使该程序饿死并失败。

红帽/CentOS的特殊策略/实时节流机制

在红帽企业版实时系统内核中,有个实时节流机制/SCHED_FIFOSCHED_RR限制实时调度的调度策略(real-time scheduler throttling)。

红帽企业版实时Linux内核带有一种保护机制,该机制使系统管理员可以分配资源配额以供实时任务使用。这个配额机制引入,算是一个保护机制。

这个机制用/proc文件系统中的两个参数控制。

/proc/sys/kernel/sched_rt_period_us定义时间周期,以微秒为单位,相当于100%的CPU资源带宽。默认值为1,000,000μs(1秒)。谨慎更改该时间段,时间段过长或太小都将危险。

/proc/sys/kernel/sched_rt_runtime_us定义所有实时任务可用的总带宽。默认值为950,000μs(0.95 s),即CPU带宽的95%。将该值设置为-1意味着实时任务最多可能占用100%的CPU时间。仅当在特殊场景实时任务经过精心设计并且没有明显的隐患(比如没有无限制的轮询循环)时,才可以这样配置。

对于实时调节机制的默认值定义的CPU时间的95%,意思是95%的cpu时间片可以通过实时任务中使用。剩余的5%将用于非实时任务(在SCHED_OTHER类似的调度策略下运行的任务)。而且需要注意,如果单个实时任务占用了95%的CPU时隙,则该CPU上剩余的实时任务将不会运行。剩下的5%的CPU时间仅由非实时任务使用。

默认值的设置带来两个好处:流氓实时任务不会通过不允许非实时任务运行来锁定系统,另一方面,实时任务最多具有95%的CPU他们的可用时间,可能会最大化效能。

聪明做法RT_RUNTIME_GREED

尽管SCHED_FIFOSCHED_RR/实时节流机制的工作原理是避免实时任务可能导致系统挂起,但是高级用户可能希望在没有非实时任务匮乏的情况下允许实时任务继续运行, 避免系统闲置。

启用后,此功能会在限制实时任务之前检查非实时任务是否饿死。如果实时任务被限制,则在系统空闲时或下一个周期开始时(以先到者为准),它将立即取消限制。

RT_RUNTIME_GREED通过以下命令 启用:

#echo RT_RUNTIME_GREED> /sys/kernel/debug/sched_features

要使所有CPU核都具有相同的rt_runtime,请禁用NO_RT_RUNTIME_SHARE逻辑:

#echo NO_RT_RUNTIME_SHARE> /sys/kernel/debug/sched_features

设置了这两个选项后,用户将保证所有CPU上的非RT任务都有一定的运行时间,同时使实时任务尽可能多地运行。

希望这个冷知识对大家有所帮助,对Linux感兴趣的同学可以看一下教程:

Linux教程:https://www.w3cschool.cn/linux/

Linux微课:https://www.w3cschool.cn/minicourse/play/linuxcourse

Linux就该这么学:https://www.w3cschool.cn/linuxprobe/

单元测试到底值不值得写?

thbcm阅读(253)

作为一名程序员,单元测试应该听过,但是很多同学没有用的过,可能是对单元测试有一些误解,例如:

  • 写单元测试需要花费更多的时间,我每天写产品代码都要加班,哪来时间写测试;
  • 写单元测试收益不大,还不是一样有bug;
  • 写单元测试有负担,改产品代码的结构,还得去改测试代码。

先尝试解答这几个问题。

写单元测试会花费更多的时间,这点描述其实不准确。准确地说,写单元测试需要花费更多「写代码的时间」,这点没什么可说的,毕竟要多写一些测试代码。但一个程序员,做一个需求的时候,花在纯写代码的时间其实不多。你得理以前代码的逻辑,设计类和方法,然后才是写代码,写完了再手动测试,可能有bug还要去debug,再修复,再测试。「真正写代码的时间,其实是很少的」。使用单元测试虽然可能占用了更多写代码的时间,但它可以帮你缩短其它时间,「会让你做这个需求花费的总时间更少」。

写单元测试会有bug吗?当然可能有了,我们是无法做到真正的bug free的,但是单元测试写好了,可以显著减小bug的数量。因为写单元测试发现bug的成本是非常低的,它可以在开发阶段就发现bug,而且可以测试很多边界的条件。但如果你「对需求和业务的认知本来都是有误」的,这是单元测试解决不了的,自然会产生bug。话说回来,写单元测试的收益远不止发现bug这么简单,它还具有「代码文档」的功能,以及「重构的安全网」存在。甚至它还可以帮你「理需求」,「设计代码」。

改产品代码需要维护对应的测试代码,这确实是带来了额外的成本。不过借助编辑器的「重构功能」,可以比较方便地批量修改需要修改的地方,其实代价没有想象中的那么大。而且重构后,再跑一遍单元测试,看哪些挂掉了,可以double-check你改的产品代码有没有问题。如果我们把单元测试当成是「产品代码的文档」来看,大概就更能够接受这个维护的成本了。

为什么需要单元测试?

前面提到,单元测试有很多功能。个人觉得单元测试最大的作用是“代码文档”和“重构安全网”。毕竟软件开发的漫长过程中,总少不了修修改改。如果没有足够的测试,改一段代码就像是在排地雷,改完后心里也总是打鼓,上线前需要先默默拜个神,生怕触发了什么bug

但如果有足够的测试(不只是单元测试),改完代码后可以跑一遍测试,看哪些挂掉了,是不是自己的改动导致的,该怎么修好测试。这样心里就有底气多了。

要知道,代码写出来是给人看的。而测试比产品代码更友好,因为它简单,直白,站在使用者的视角来描述,所以如果想要了解一段产品代码具有什么功能,看它的单元测试会更直观,更舒服。

很多团队会做测试,但绝大多数测试的工作是在开发后,由专门的测试同学去负责端到端的测试或者API测试。其实端到端的测试成本是非常大的,尤其是对于某些边界条件,构造数据和场景是非常麻烦的。而且一旦发现了bug,再去沟通,修改,提交,部署,需要花费很多时间。

而单元测试最大的优势就是“成本低”,想要测试产品代码的每个分支都比较容易,而且单元测试一般是开发同学自己写,可以用最小的时间发现bug,用最低的成本修改bug

什么是单元测试?

测试金字塔

并不是所有测试都是单元测试,测试其实分成很多种。业界比较广泛传播的“测试金字塔”描述了它们的区别和关系:

从测试金字塔模型来看,越在底层的测试,覆盖面应该更广,成本更低。单元测试处于测试金字塔的最低端,是整个测试金字塔的基础。

当然了,测试金字塔并不一定只有三层,中间可能会有其它的测试,比如“契约测试”等。

单元测试的特点

单元测试就像它的名字一样,“单元”(Unit),足够小,足够快,无依赖。单元测试只测你想测的那部分产品代码的逻辑,一个单元测试应该只测一个简单的业务逻辑。一般来说,运行一个单元测试是很快的,基本上在几毫秒到几十毫秒之间。如果有依赖的类,可以mock其他类,消除外部依赖。

什么不是单元测试?

很多同学容易将其他测试与单元测试搞混,最常见的是会启动Spring上下文的集成测试。比如使用@SpringBootTest注解可以启动Spring上下文,这可以测试依赖是否正常注入等Spring的功能,但运行一次需要耗费很多时间(因为要启动Spring上下文),也并不是真正的“单元测试”,因为它依赖了Spring框架。

如何写单元测试

那具体如何写单元测试呢?我们业界有一个叫做「TDD」(测试驱动开发)的方法论。TDD的核心在于“驱动”二字,它的理念是从测试视角出发,通过测试驱动出来产品代码。而在测试金字塔中,单元测试与开发人员最息息相关,所以这里的“测试”一般是指的单元测试。

TDD大概分这几个步骤:

  1. 理清需求
  1. 设计类和方法的出参和入参
  1. 写测试代码
  1. 驱动出产品代码
  1. 重构,循环3-5步。

首先要理清楚需求,因为只有理清楚了需求,才能保证我们使用TDD驱动出来的代码是跟业务期望的一致的。然后第二步是设计类和方法的过程,也称为Task List。这一步可以设计好类与类之间的关系,方法的出参和入参。其实不使用TDD也会有前面这两个步骤,只不过使用TDD的话,可以帮助你更好地从业务视角出发,先把该设计的东西都设计好,避免直接上手写代码,写到一半的时候觉得不对,再去改。

3-5步其实是一个循环的过程。因为刚开始写代码可能并没有太注意代码的格式、风格、性能,一气呵成写得比较快,让测试通过。等测试通过后,可以回过头来重构一下之前写的代码,重构后再跑一遍所有的单元测试,看是否有挂掉的单元测试,以此来检测重构是否对期望的输入输出有影响。

单元测试的结构

一个完整的单元测试,应该分为4个部分:

  1. 声明和参数
  1. 准备入参和mock
  1. 调用产品代码
  1. 验证,也叫断言

Java来说,单元测试框架有几个,最流行的应该是JUnitTestNG。笔者使用JUnit多一点,JUnit使用@Test注解在方法上来声明一个测试。JUnit最新版本是JUnit 5JUnit 5相较于上一个版本,在参数化测试方面做了很多改进,这样我们就不用写很多个高度相似的测试方法了(关于JUnit 5参数化测试,大家可以查看官方文档,也有对应的中文翻译,很方便阅读)。

一般来说,方法名需要尽可能可读,它可能比较长,但能够清晰地表述这个测试的意图,比如:

@Test void shouldReturn5WhenCalculateSumGiven2And3() {}


@Test
void should_return_5_when_calculate_sum_given_2_and_3() {}

具体使用驼峰命名法还是下划线,根据自己团队的规范来就好,尽量所有测试风格保持一致。(个人更喜欢下划线~)

入参一般是基本类型或者POJO对象,有些参数可以抽成变量,后面在验证阶段可能用得上。

如果产品代码有外部依赖,就需要用mock来消除外部依赖。常见的Mock框架有EasyMock、「Mockito」等,大家可以对比一下各个mock框架的区别,选择一个合适的。

很多同学刚开始写单元测试的时候不能理解为什么需要mock,觉得mock比较麻烦,甚至有点多此一举的感觉。其实不然,mock的意义在于,你「可以保证你的测试只测试了你要测的那部分代码」。这样如果测试不通过,你就可以知道一定是要测的那个方法有问题,不可能是外部依赖的问题,这样才能做到真正的“单元”化,才能保证每个测试足够小,足够纯粹。

准备好入参和mock后,会显式地调用一下要测的那个方法,这个一般只有简单的一行。

最后是验证,验证分为好几种,最常用的是验证出参是符合自己期望的。也有时候会验证异常等边界情况。JUnit等测试框架基本上自己带了验证的功能,但API都比较简单,个人感觉不是特别好用,推荐使用「AssertJ」,功能强大,API用起来也比较舒服。

举个例子吧:

@Test void shouldReturnUserWithOrgInfoWhenLoginWithUserId() { String userId = “userId”; String orgId = “orgId”; User user = UserFactory.getUser(userId); Org org = OrgFactory.getOrg(orgId); given(orgService.getOrgById(orgId)).willReturn(org);

    
    UserInfo userInfo = userService.login(userId);

    
    assertEquals(org, userInfo.getOrg());
}

单元测试常见问题

下面聊一聊单元测试常见的一些问题。

先写测试还是先写产品代码?

都可以。虽然有一种说法是TDD推荐的是先写测试,再写实现。但很多刚开始写单元测试的同学并不习惯这种方式。先写测试有一个好处,可以让你在设计代码的时候从业务视角去思考,而不是代码实现视角。大家可以尝试先写测试再写实现,体会一下这种感觉。

写单元测试需要花费大量额外的时间?

这个其实在文章开篇已经讨论过了。写单元测试确实会花费更多的“写代码”的时间,但是总的来说,它可以缩短整个需求开发周期的时间。所以写单元测试完全是一笔“划算的生意”。

什么代码最需要单元测试?

不自信的代码,逻辑复杂的代码,重要的代码。比如工具类、三层架构的Service层、DDD的聚合根和领域服务等,这些都应该写足够的单元测试。

入参对象构造太麻烦?

构造一个合适的入参对象比较麻烦,尤其是有些对象有非常多的参数,如果每个测试都要去从头构造的话,会让测试代码变得非常臃肿,可读性变差。这个时候可以使用工厂类来批量生产对象。这个工厂类放在测试目录下,并不会对生产代码造成影响。前面的例子里面,UserFactory就是一个User对象的工厂类。

返回值为void测什么?

返回值为void,说明方法没有出参,那方法内部必然有一些行为,它可能是「改变了内部属性的值」,也可能是「调用了某个外部类的方法」。

如果是改变内部的某个值,那可以通过对象的get参数来断言。这在使用DDD后的领域模型是一个问题,因为有可能本来产品代码不需要暴露出get方法的,但由于测试需要,暴露出了内部属性的get方法。虽然使用反射也可以拿到内部属性的值,但没有太大必要。权衡利弊,还是暴露领域模型的get方法好一点。

如果是调用某个外部的方法,可以用verify来验证是否调用了某个方法,可以用capture验证调用其它方法的入参。这样也可以验证产品代码是否如自己预期的设计在工作。

static方法如何mock?

static方法不好mock,需要用特殊的mock框架。比如PowerMockJMockit。一般来说,Utils类的方法很多是static的,我们用得很多的时间类LocalDateTime,获取当前时间,也是static的。这个时候需要用专门的mock框架来mock一下。

多线程如何测试?

多线程也不好测试。如果程序简单,可以用「睡眠」或者CountDownLatch等多线程工具类来辅助测试,等所有线程跑完,再统一验证。

如果程序相对复杂,需要使用专门的多线程测试框架,比如tempus-fugitThread WeaverMultithreadedTC、以及OpenJDKjcstress项目等。

关于具体的框架如何使用,以后有时间可以写一篇常用的注解的介绍。其实官方文档里面都有写,大家照着官网写几个例子就会了。比较推荐的基础套餐是junit 5 + mockito + assertj。关于static方法和多线程测试框架,大家有需要的时候再去了解也行。

想了解更多测试的看一下相关教程:

软件测试:https://www.w3cschool.cn/software_testing/

文章参考来源:www.toutiao.com/a6856755990545891848/

Linux的发展史:Stallman的GNU计划

thbcm阅读(225)

本文要说的是一个传奇人物————Richard Matthew Stallman,就是下图里这位不爱刮胡子的大叔。

Richard Matthew Stallman,1953年出生在美国纽约曼哈顿地区。在他生命的前十几年中,他并没有表现出什么过人的地方,但那是因为他没遇到一个叫做电脑的东西。

1 快乐的自由

高中的一个暑假,他去给IBM打工,花了两周的时间用Fortran语言编了一个数据处理的程序。这是他第一次接触计算机,或许就是这次相遇,确定了他未来行走的方向。1971年,他考上了哈佛大学,上学的同时,他还受聘于麻省理工学院的人工智能实验室,成为了一名职业黑客(黑客这个词没有贬义)。在人工智能实验室期间,他可没少干活,开发了很多有用的软件,其中最著名的就是Emacs编辑器。Emacs是一个可与Vi相抗衡的强大的编辑器。两者的操作方式完全不同,但同样强大,各自用自己独有的方式,提高着人们的编辑效率。直到今天,仍然有人争论到底Emacs好还是Vi好,信奉Emacs的人和信奉Vi的人形成了两个帮派,这两个帮派经常在互联网上用鼠标键盘相互灌水拍砖,拼个你死我活。哦,扯远了,咱还回来说Stallman

那时候的Stallman在人工智能实验室里工作得非常愉快,大家有BUG同当,有代码共享。那时候的软件工程师的世界,是一个“人人为我,我为人人”的理想世界。因为最初的计算机软件没有什么开源不开源的概念,那时候的软件天生就是自由的!卖计算机的同时会附带软件,包括软件的源代码和文档。计算机厂商卖的主要是计算机的硬件,软件只是附属品而已。用户可以根据自己的需要去修改软件,与别人分享软件。总之,软件是用户花钱买硬件时附带着买来的,用户想怎么玩就怎么玩。软件开发者的目的,也不是靠软件赚钱,而是靠软件支撑起硬件的功能,然后靠卖硬件赚钱。

2 自由逐渐远去

然而随着技术的发展,软件逐渐脱离硬件成为一个独立的产业,很多软件慢慢地只提供二进制代码而不提供源代码了,这就意味着你不能修改它,并且多数软件还规定最终用户没有二次分发的权利。也就是说,这东西你买了,只能你用,你再给别人就不行!这就好像我买了把菜刀,然后卖菜刀的告诉我“你这把菜刀不许借给你的邻居用,也不许私自给菜刀换刀把,否则我就告你!”

Stallman当时就遇到了类似这样的菜刀问题。那时候,他们实验室买的第一台打印机附带有驱动程序的源代码。他们那的黑客们可以随意修改这个驱动,根据自己的需要添加些小功能,改改BUG之类的,这为他们的工作带来了很大的方便。后来,实验室又买了一台激光打印机,这次厂商只提供了二进制的打印机驱动程序,它是实验室里仅有的一个没有源代码的软件。Stallman很不喜欢这样的产品,然而他没有选择,只能沉默。

后来出于工作的需要,Stallman想修改一下这个驱动程序,但是不行,没源代码啊。Stallman听说卡内基·梅隆大学有这个打印机的驱动程序源代码,他就去了那里,跟他们套近乎:“那啥,大家都是道上混的,谁还没个”马高蹬短”的时候?是兄弟的拉哥们儿一把,我也没啥事儿,就是我们那打印机老丢字,老把一些关键的字打成口口,我估计是驱动的问题,听说你们这有这驱动的源代码,能不能给我拷一份?”对方办事效率还是挺高的,很干脆地拒绝了他。因为他们和厂商签署了一份保密协议,协议要求他们不能向别人拷贝源代码。Stallman顿时感到他们背叛了自由的计算机社团,他非常生气,但是他没有办法改变什么,只好又选择了沉默。

这只是一件小事,只是一个时代的缩影。那个时代,正处在软件向私有化转变的过程中,也是软件逐渐商业化的过程。越来越多的软件选择了不开放源代码,不允许二次分发的发布方式。Stallman身边的同事,一个一个地跑到开发私有软件的公司去打工了,他们不再相互分享,不再相互交流。Stallman问:“你们那软件的查找算法做得不错啊,怎么实现的?”“对不起,无可奉告。”“你们的文档工具效率挺高啊。”“对不起,商业机密。”……面对这一切,Stallman又能说什么呢?他还是只有沉默。

3 不在沉默中爆发,就在沉默中灭亡

Stallman爆发了!他不能容忍软件世界里清新自由的空气被私有软件污染;他不能容忍被剥夺按照自己的需求修改软件的权利和乐趣;他不能容忍自己买条皮带尺寸不够时,自己竟然连在上面多打个洞的权利都没有!于是,他就爆发了。

他要重现当年那“人人为我,我为人人”的合作互助的软件世界;他要把使用、复制、研究、修改、分发软件的权利还给软件世界的每一个人民;他要用自己的行动告诉人们,软件天生就该是自由的!

他要开辟一个新的世界,哪怕是一个人在战斗!于是,一个宏伟的计划——GNU计划在他心中产生了。它的目标是创建一套完全自由的操作系统。因为操作系统是电脑中最重要、最基础的软件,要创造自由的软件世界,自然先要有一套自由的操作系统,然后再以此系统为中心,开发各种各样自由的软件。1983年,Stallmannet.unix-wizards新闻组上公布了GNU计划,这个计划的标志是一头角马(也就是非洲牛羚),就是下图所示的这个。

提示:*GNU 是 “GNU is Not UNIX”的递归缩写,Stallman表示这个词应该读作/'gnu:/(发音类似“革奴”),以区别于表示非洲牛羚的单词gnu(发音与“new”相同)。

这个计划要创造一套自由的类UNIX操作系统。系统本身及系统上的软件都是自由软件,它们可以被免费获取,随意使用、修改和再分发。并且每个人都可以获得这个系统全部的源代码,每个人都可以为完善这个系统作出自己的贡献。这个系统要使用与UNIX相同的接口标准,这样,就可以由不同的人,分期分批地创作操作系统的不同部分而不必担心相互之间协同工作的问题。

4 实现GNU梦想

为了实施GNU计划,1985年,Stallman又创建了自由软件基金会。基金会的主要工作就是执行GNU计划,开发更多的自由软件。1989年,Stallman与基金会的一群律师们起草了广为使用的《GNU通用公共协议证书》也就是GPL协议,以此协议来保证GNU计划中所有软件的自由性。到了1990年,GNU计划中的这个系统已经初具规模,有了很多优秀的软件。其中有很多是世界各地的黑客们无偿提供的,也有一部分是利用自由软件基金会的基金雇用程序员来开发的,当然,Stallman自己也身先士卒,开发了EmacsGCCGDB等重要软件。当他看着这些丰富的自由软件的时候,感觉到那清新自由的空气,终于又回来了,以后,人们就可以拥有一个可以自由使用、自由修改、自由分发的、自由的操作系统了!不过等一下,好像还差点什么,哦,还……差个内核吧。

作为一个系统,没有内核是不行的,这么重要的部件Stallman当然不会忘记,所以才会有Hurd内核。这个内核被设计为一个遵守POSIX标准的微内核。所谓微内核,是相对于宏内核来说的。宏内核就像我们现在的Linux内核,是一个独立的程序,里面包含了进程管理、内存管理、文件管理等功能。而微内核则将一个内核需要的功能尽量地简化并且拆分,运行起来是几个独立的程序,有的专门负责进程管理,有的专门负责内存分配。内核是一个系统的核心,所以至关重要,StallmanHurd的开发也是精益求精,非常谨慎,以至于内核的进度有些落后于其他的系统软件,当其他软件都已经有比较优秀的版本的时候,Hurd内核依然不能够走出实验室投入真正的使用。这种情况一直持续到1991年,另一位英雄的出现——不过,这里先卖个关子,暂且不去说他。

无论怎样,到今天,Stallman理想中的自由世界,终于拉开了那沉重的幕布,展现出了自由的光彩。而Stallman并不满足,也确实没有满足的理由,这个自由的世界还需要成长,还需要更加丰富多彩,还需要有更多的人走进这个世界中来。于是Stallman奔走于世界各地,告诉人们有这么一个自由的世界,号召人们加入这个世界,鼓励人们为使这个世界更加自由而付出自己的力量。他是一个执着的苦行僧,为了他的梦想,为了他的自由世界,他会一直走下去……

以上就是Linux的发展史,Stallman和他的GNU计划。希望能扩展大家的知识面,然后对Linux有兴趣的同学可以看一下教程:

Linux教程:https://www.w3cschool.cn/linux/

Linux微课:https://www.w3cschool.cn/minicourse/play/linuxcourse

Linux就该这么学:https://www.w3cschool.cn/linuxprobe/

刷新你对进度条的认识,用python写出不一样的进度条

thbcm阅读(207)

1 简介

在日常工作中,我们运行程序经常会用到「循环迭代」,假如这个执行时间很短,那倒也无所谓。但是有一些过程耗时蛮长的,给其加上「进度条」(progress bar),可以帮我们监控代码执行进度,以及过程出现异常的情况,非常实用。这里为大家介绍Python中非常实用又风格迥异的两个进度条相关库——tqdmalive-progress的主要用法。

2 tqdm常用方法

tqdmPython中所有进度条相关库中最出名的,既然是最出名的,自然有它独到之处。

tqdm不仅可以生成基础的可在终端中显示的进度条,还可以配合jupyter notebookjupyter lab生成更加美观的网页「交互」部件形式的进度条,更是和pandas强强联手,为pandas中的一些操作提供专有的进度条功能。

下面我们来对tqdm的主要功能进行介绍。

2.1 基础用法

因为是第三方库,首先需要利用pip install tqdmconda install -c conda-forge tqdm对其进行安装,安装完成后先来看看它最基本的用法:

利用tqdm.tqdm,将for循环过程中进行迭代的对象简单包裹,就实现了为循环过程添加进度条以及打印执行速度、已运行时间与预估剩余运行时间等实用信息的功能,同样也可用于「列表推导」:

而针对迭代对象是range()的情况,tqdm还提供了简化版的trange()来代替tqdm(range())

其附带的参数desc还可以帮助我们设置进度条的说明文字:

而如果想要在迭代过程中变更说明文字,还可以预先实例化进度条对象,在需要刷新说明文字的时候执行相应的程序:

但当迭代的对象长度一开始未知时,譬如对pandas中的DataFrame.itertuples()进行迭代,我们就只能对其执行速度等信息进行估计,但无法看到进度条递增情况,因为tqdm不清楚迭代的终点如何:

2.2 配合jupyter notebook/jupyter lab的美观进度条

tqdmjupyter notebookjupyter lab有着特殊的支持,且使用方法非常简单,只需要将原有的from tqdm import XXX的相应功能导入格式修改为from tqdm.notebook import XXX就可以了,以trange为例:

2.3 配合pandas中的apply

tqdmpandas中的apply()过程提供了特殊的支持,因为pandas中的apply()本质上就是串行循环运算,你可以将pandas中的任何apply操作替换为progress_apply,并且记住每个单独的progress_apply前要先执行tqdm.pandas(),就像下面的例子一样:

3 alive-progress常用方法

虽然与tqdm一样都是为了给循环过程加上进度条而诞生的库,但alive-progress相比tqdm增加了更多花样繁多的动态效果,我们通过调用其专门提供的showtime()函数可以查看所有可用的动态进度条样式:

同样类似地可以查看所有进度条样式:

使用起来也是非常简单,但与tqdm用法区别很大,需要配合with关键词,譬如下面我们使用到alive_progress中的alive_bar来生成动态进度条:

通过修改bar参数来改变进度条的样式:

比较遗憾的是目前的alive-progress只能在终端中运行,还没有为jupyter开发更美观的交互式部件,但你可以在譬如网络爬虫等任务中使用它,效果也是很不错的。然后想学习python的同学可以看一下教程。

python教程:https://www.w3cschool.cn/python/

python3基础微课:https://www.w3cschool.cn/minicourse/play/python3course

5 种方法带你查看Linux系统服务

thbcm阅读(242)

Linux 系统服务有时也称为守护程序,是在Linux启动时自动加载并在Linux退出时自动停止的系统任务。

在本文中将为大家介绍如何列出 Linux 系统里所有运行的服务,以及如何检查某个服务的当前状态。

Centos/RHEL 7.X 的 systemd 系统服务查看

CentOS 7.x开始,CentOS开始使用 systemd 服务来代替 daemon ,原来管理系统启动和管理系统服务的相关命令全部由 systemctl 命令来代替。

systemctl list-unit-files

命令的输出结果如下:

查看所有运行着的 systemd 服务可以运行以下命令:

systemctl | more

命令的输出结果如下:

除此之外,你还可以使用以下命令:

systemctl list-units –type service

命令的输出结果如下:

如果你想要在结果里搜索某个特定的服务,可以使用管道及 grep 命令。

systemctl | grep “apache2”

命令的输出结果如下:

使用 netstat 命令查看系统服务

Netstat 命令是用来检查活动的网络连接、接口统计分析,以及路由表状态。这个命令在所有的 Linux 发行版都可用,我们接下来就用它来查看系统服务。

查看服务及它们所监听的端口:

netstat -pnltu

命令的输出结果如下:

通过系统服务配置文件查看系统服务

服务的配置文件是 /etc/services 是一个 ASCII 文件,它包含了一系列的用户程序可能用到的服务。在这个文件里,包括了服务名称,端口号,所使用的协议,以及一些别名。

对于这个文件,我们可以使用任意的文本工具查看,比如vim

vim /etc/services

命令的输出结果如下:

查看 systemd 服务状态

在一些新版的 Linux 系统,已经有些用 systemd 来取代 init 进程了。在这种系统里,如何去查看系统服务呢?我们可以使用以下语法:

systemctl status service_name

比如说,查看你系统上的 OpenSSH 是否在运行,可以运行:

systemctl status sshd

命令的输出结果如下:

或者,你也可以使用以下命令格式去查看某个服务是否正在运行:

systemctl is-active service_name

如果使用这条命令的话,实现上面那个例子对应的命令为:

systemctl is-active sshd

命令的输出结果如下:

同时,你也可以查看一个服务是否已经被使能了,可以使用以下命令:

systemctl is-enabled service_name

比如,检查 OpenSSH 服务是否已经使能,可能输入以下命令:

systemctl is-enabled sshd

命令的输出结果如下:

早期版本的服务状态查看

其实也不能说早期,现在依然还有很多这样的系统,上面跑着 SysV init 进程。对于这种系统,查看服务状态的命令为:

service service_name status

还是查看 OpenSSH 状态的例子,对应的命令为:

service sshd status

命令的输出结果如下:

你也可以使用以下命令来查看所有的服务状态:

chkconfig –list

命令的输出结果如下:

以上就是查看Linux 系统服务的 5 大方法了。希望对大家有所帮助,对Linux有兴趣的同学可以看一下教程

Linux教程:https://www.w3cschool.cn/linux/

Linux微课:https://www.w3cschool.cn/minicourse/play/linuxcourse

Linux就该这么学:https://www.w3cschool.cn/linuxprobe/

带你认识现代C++,看看C++11常用特性

thbcm阅读(199)

C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,所以本文就是对带你了解一下C++11的一些新特性。

C++ 是一个拥有用户群体相当大的语言,同时也在多个领域使用的语言。从 C++98 的出现到 C++11 的正式定稿经历了长达十年多之久的积累。

C++11C++ 的第二个主要版本(前一个是 C++98 而后一个是 C++17,C++14则是C++11的一次小的改进 ),并且是从 C++98 起的最重要更新。它引入了大量更改,标准化了既有实践,并改进了对 C++ 程序员可用的抽象。

C++14/17 则是作为对 C++11 的重要补充和优化, C++20 则将这门语言领进了现代化的大门,所有这些新标准中扩充的特性,给 C++ 这门语言注入了新的活力。

C++11是对目前C++语言的扩展和修正, C++11不仅包含核心语言的新技能,而且扩展了C++的标准程序库(STL) ,并入了大部分的C++ Technical Report 1(TR1) 程序库(数学的特殊函数除外)。

C++11关键新特性:

核心语言功能特性

  • auto 与 decltype
  • 预置与弃置的函数
  • final 与 override
  • 尾随返回类型
  • 右值引用
  • 移动构造函数与移动赋值运算符
  • 有作用域枚举
  • constexpr 与字面类型
  • 列表初始化
  • 委托与继承的构造函数
  • 花括号或等号初始化器
  • nullptr
  • long long
  • char16_t 与 char32_t
  • 类型别名
  • 变参数模板
  • 推广的(非平凡)联合体
  • 推广的 POD (平凡类型与标准布局类型)
  • Unicode 字符串字面量
  • 用户定义字面量
  • 属性
  • lambda 表达式
  • noexcept 说明符与 noexcept 运算符
  • alignof 与 alignas
  • 多线程内存模型
  • 线程局部存储
  • GC 接口
  • 范围 for (基于 Boost 库)
  • static_assert (基于 Boost 库)

库功能特性

头文件

<typeindex> <type_traits> <chrono> <initializer_list> <tuple> <scoped_allocator> <cstdint> <cinttypes> <system_error> <cuchar> <array> <forward_list> <unordered_set> <unordered_map> <random> <ratio> <cfenv> <regex> <atomic> <thread> <mutex> <future> <condition_variable>

库功能特性

  • 原子操作库
  • emplace() 与其他遍及既存标准库所有部分的右值引用使用
  • std::unique_ptr
  • std::move_iterator
  • std::initializer_list
  • 有状态及有作用域分配器
  • std::forward_list
  • chrono 库
  • ratio 库
  • 新算法
  • Unicode 转换平台
  • thread library
  • std::exception_ptr
  • std::error_code 与 std::error_condition
  • 迭代器改进:
  • std::begin
  • std::end
  • std::next
  • std::prev
  • Unicode 转换函数

一款新的APP开发框架–Flutter

thbcm阅读(220)

由于物联网的快速发展,新的APP开发热潮正在到来,谷歌、华为这些公司正在重新定义移动开发框架,而Flutter就是可选择之一。不敢说Flutter一定是移动技术的未来,但一定代表了移动技术的发展方向。

一、Flutter是什么

FlutterGoogle 于 2015 年 5 月 3 日推出的免费开源跨平台开发框架,可以快速在iOSAndroid上构建高质量的原生用户界面。Flutter旨在帮助开发者使用一套代码开发高性能、高稳定性、高帧率、低延迟的AndroidiOS应用。Flutter使用的是 Google 自己开发的网络编程语言——Dart语言

二、Flutter 的发展

2015年 05 月Flutter的第一个版本“Sky”在Dart开发者峰会上亮相;

2018年02月27日在世界移动大会 (MWC)上宣布了第一个 Beta 版发布;

2018年03月06日Beta 2版本发布;

2018年12月05日谷歌发布 Flutter 1.0 正式版;

2019年9月谷歌发布Flutter 1.9

目前很多大公司、开发者纷纷转型使用和学习 Flutter 进行跨平台应用的开发。全世界已经有多个公司开始使用 Flutter 来开发应用,包括 Abbey Road Studios阿里巴巴Capital OneGrouponHamilton京东Philips HueReflectly 以及腾讯等。Flutter 1.9于 2019 年 9月发布,这样的更新频率给开发者和公司增加了动力,Flutter 势必将成为未来跨平台开发主流趋势。

三、Flutter框架特性

快速开发

Flutter的热重载能力帮助开发者快捷方便的试验、重构UI、添加特性和修复bug。在仿真器、模拟器、ios、android硬件上体验亚秒级的重载,而不会丢失状态。

跨平台

Flutter基于图像绘制引擎进行渲染,在不同平台下绘制效果绝对一致,能做到真正的跨平台。

绚丽UI

通过Flutter内建的、漂亮的、有质感设计的Cupertino(ios-flavor)小工具、丰富的动画API,平滑的自然滚动和平台感知,让用户感受UI设计的快乐。

响应式

通过Flutter的现代响应式(Reactive)框架、丰富的平台布局、基础组件,能够轻松的构建用户界面。使用强大而灵活的API解决2D、动画、手势、效果等难题。

访问原生功能

通过平台api第三方sdk原生代码,使应用变得生动。Flutter可以重用现有的javaswiftObjc代码,并在iOSAndroid上访问原生特性和SDK

四、目前各种跨平台方案的对比

开发APP应用,如需要同时兼容iOSAndroid两种平台,有两种技术选择:

1、走原生开发路线,把界面和逻辑在不同平台分别实现;

2、用同一套代码兼容多个平台,但这往往意味着运行速度和产品体验的损失。

除了原生外,目前跨平台技术一般是混合开发,如采用 H5React NativeWeex、小程序等技术实现跨平台应用。不过这些混合开发,或多或少都能感觉到UI卡顿和体验不流畅,并且开发和学习成本非常高,有各自的局限性。

Flutter的出现,为开发者提供了一套两全其美的解决方案:既能用原生代码直接调用的方式来加速图形渲染和 UI 绘制,又能同时运行在两大主流移动操作系统上,并且体验和流畅度和原生基本一致、开发效率非常高、学习难度和成本低。

从上面的对比可以看出,Flutter 优势明显:高体验度、高开发效率、低学习成本、高可扩展性。未来 Google Flutter团队还将会使 Flutter 支持 PCWeb 的跨平台开发,实现真正全平台。 针对Flutter的跨平台特性,与react nativeweex做对比可以发现,其性能碾压后两者,采用自带Skia绘制引擎,性能堪比原生。

以上就是关于Flutter的一些介绍了,对Flutter感兴趣的同学可以看一下教程:

想在JavaScript更进一步,你需要掌握这 36 个概念

thbcm阅读(219)

你可能会经常听到一些人在抱怨 JS 很奇怪,有时甚至是一文不值。之所以有这种想法,是因为他们不太了解 JS 背后的运作方式。我也觉得 JS 在某些情况处理方式与其它语言不太一样,但这并不能怪它,它也只是以自己的方式展现给大家而已。

如果,你热爱一门编程语言,那么应该就会想深入了解并逐个掌握它的概念。

这里列出了36个JavaScript概念,你需要掌握这些概念才能成为一个更懂 JS 的前端开发者。

1.调用堆栈执行

我们都知道堆栈溢出,但是你知道堆栈溢出是由什么原因导致的吗? 堆栈溢出是与调用堆栈一些操作错误相关联的。

理解了调用堆栈,你就会清楚解像是JS 这们的编程语言是如何执行的。

2. 原始数据类型

const foo = "bar";
foo.length; // 3
foo === "bar"; // true

这里,我们将值bar分配给常量foo时,它属于原始类型string。这个每个人都知道。但是各位少侠想没想过一个问题,string是基本数据类型,怎么能调用方法了?

奇怪吗? 不。

这个特性称为自动装箱。每当读取一个基本类型的时候,JS 后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。

还是拿上面的例子开始:

const foo = "bar";
foo.length; // 3
foo === "bar"; // true

变量 foo 是一个基本类型值,它不是对象,它不应该有方法。但是 JS 内部为我们完成了一系列处理(即装箱),使得它能够调用方法,实现的机制如下:

  • 创建String类型的一个实例
  • 在实例上调用指定的方法
  • 销毁这个实例
const foo  = new String("bar");
foo.length
foo === 'bar'
foo = null

通过对原始数据类型有深入的了解,我们就应该知道这些“怪异”情况是如何发生的,以及它们背后的逻辑原因。

3.值类型和引用类型

最近,我对 “引用传递” 在 JS 是怎么工作的感到困惑。尽管我知道 CJava 等语言中有“按引用传递”和“按值传递”的概念,但是我不确定它在 JS 中如何工作。

你是否知道分配给 JS 中非原始值的变量对该值的引用?引用指向存储值的内存位置。。

var arr1 = [1,2,3];
var arr2 = arr1;
arr2.push(10);
console.log(arr2);
//[1, 2, 3, 10]
console.log(arr1);
//[1, 2, 3, 10]

上面的示例中可以看到,对arr2所做的任何修改也将体现在arr1上。这是因为它们仅保存值对应的内存地址的引用,而非值本身。

通过了解值类型和引用类型的概念,你就会更好地了解如何为变量分配值和内存引用。

4. 强制类型转换

这个概念主要解释了隐式和显式类型强制之间的区别。这是前端开发中对 JS 迷惑少数几个领域之一。对于隐式强制转换的概念尤其如此,因为它对不同的数据类型以不同的方式表现。

这是 JS 面试中最常被考的。

Number('789')   // 显式
+'789'          // 隐式
789 != '456'    // 隐式
9 > '5'         // 隐式
10/null          // 隐式
true | 0        // 隐式

掌握了类型显隐转换,恭喜你对 JS 了解就进一步了。

5. 比较运算符号 和 typeof 运算符

双等与三等,它们在大多数情况下在表面上看起来相同并且给出相同的结果,但是,它们有时候可能会给带来意想不到的错误。

为了了解这两亲兄弟的区别,我们可以借助 typeof 来查看被比较的值的类型。

typeof 3 // "number"
typeof "abc" // "string"
typeof {} // "object"
typeof true // "boolean"
typeof undefined // "undefined"
typeof function(){} // "function"
typeof [] // "object"
typeof null // "object"

6. JavaScript 作用域

作用域是 JS 中一个很重要的尴尬,JS 也一直在不断完善自己的作用域。根据Wissam的说法,作用域的简单定义是,编译器在需要时查找变量和函数。

了解作用域有助于我们有效地使用JavaScript。我们还需要了解全局作用域以及块和函数作用域,也称为词法作用域。JS 作用域一开始接触会感到很困惑,但是一旦你了解了事情的幕后原理,使用它就会非常令人兴奋。

(推荐教程: JavaScript教程

7. 语句和声明

JavaScript 程序是一系列可执行语句的集合。所谓语句,就是一个可执行的单元,通过该语句的执行,从而实现某种功能。通常一条语句占一行,并以分号结束。默认情况下,JavaScript 解释器按照语句的编写流程依次执行。如果要改变这种默认执行顺序,需要使用判断、循环等流程控制语句。

我们应该要知道 语句和声明 的区别,这对我们全面了解 JS 是很有帮助的。

8. 立即调用的函数表达式和模块

IIFE: Immediately Invoked Function Expression,意为立即调用的函数表达式,也就是说,声明函数的同时立即调用这个函数。它主要用于避免污染全局作用域。后来,引入了ES6模块,为避免全局作用域的污染提供了一种标准方法,尽管有人认为它不是IIFE的直接替代。

通过理解IIFE和模块,你可以构建较少由于全局空间处理不当而导致的错误的应用程序。当然,使用模块,我们还可以做很多事情。

9.消息队列和事件循环

正如MDN文档所说,JavaScript 有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 CJava

在并发模型中,消息队列用于处理最早的消息。只要有事件发生,便会加入消息队列中。通过理解这些概念,你可以更好地理解JS在底层是如何工作的,以及知道你的代码是如果运行的。

10.时间间隔

想在 JS 有计划的调用的函数,可以使用下面两个函数:

  • setTimeout 允许我们在特定时间间隔后运行一次函数。
  • setInterval允许我们重复运行一个函数,从一个特定的时间间隔开始,然后以该间隔连续重复。 这些与前面的消息队列和事件处理程序的概念有些关联。因此,通过理解时间间隔方法,我们可以理解它们是如何工作的,并在我们的用例中有效地使用它们。

11.JS 引擎

JavaScript引擎是执行 JS 代码的计算机程序或解释器。JS 引擎可以用多种语言编写。例如,驱动Chrome浏览器的V8引擎是用 c++ 编写的,而驱动Firefox浏览器的SpiderMonkey引擎是用 Cc++ 编写的。

要想编写高效的代码,你必须了解所使用的 JS 引擎。使用webview的移动开发人员要特别注意这一点。

12.按位运算

按位运算操作将值视为位(0和1),而不是十进制,十六进制或八进制数字。按位运算符对此类二进制表示形式执行其操作,但是它们返回标准JavaScript数值。

通常,很少会在代码中使用这些操作,但是它们确实有一些用例。比如,可以使用它们来查找偶数和奇数值,颜色转换,颜色提取等等。

通过全面了解这些按位操作,您可以很好地使用 WebGL 之类的技术,因为它包含许多像素操作。

13. DOM 和布局树

我们大多数人都听说过文档对象模型(DOM),但只有少数人对此有深入的了解。你知道在浏览器中看到的不是DOM吗?而是渲染树,它实际上是DOMCSSOM的组合。

通过理解DOM的工作方式、结构以及页面的渲染方式,我们就能够在 JS 的帮助下动态地操作web页面。这对于确保我们的应用程序具有高标准的性能尤为必要。

14.类和工厂

JavaScript 不是一种面向对象的语言。但是,为了模仿OOP属性,使用了构造函数。根据Tania的说法,“JavaScript中的类实际上并没有提供其他功能,只是在原型和继承上提供语法糖,因为它们提供了更简洁,更优雅的语法。由于其他编程语言都使用类,因此 JS 中的类语法使开发人员在各种语言之间移动变得更加简单。”

工厂函数是不是返回对象的类或构造函数的函数。根据JS专家Eric Elliot的说法,“在JavaScript中,任何函数都可以返回一个新对象。如果它不是构造函数或类,则称为工厂函数。”

当开始开发规模更大的应用程序时,理解这两个概念是很有必要的。

15.this 关键字和 apply,call及bind方法

就我个人而言,我认为对于一个JS开发人员来说,理解this 关键字是至关重要的。如果你不能正确地理解它,将来你开发的项目也会经常遇到this相关的问题。

如果你对this关键字很清楚,则可以看看applycallbind方法,这些都可以解决 this 指向引发的问题。

16.构造函数和 “instanceOf” 运算符

构造函数就像常规函数一样。但是它们有很多差异,函数名称以大写字母开头,并且只能由new运算符执行。具有OOP开发经验的程序员会熟悉new关键字。

为了正确识别对象的类型,我们使用instanceOf运算符。简单来说,它检查一个对象是否是另一个对象的实例。

这才助于你理解对象如何相互继承,继承是通过原型实现的。

17.原型

这是 JS 中最令人困惑的概念之一,即使对于有十年经验的人来说也是如此。

JavaScript中的原型是在对象之间共享通用功能的机制。JavaScript中几乎所有对象都是Object的实例。对象会从Object.prototype继承所有属性和方法。

简单来说,原型是 JS 对象从中继承方法和属性的对象。

理解了原型,你就可以构建高效,快速的应用程序。

(推荐微课:JavaScript微课

18. 使用 new,Object.create 和 Object.assign 创建对象

创建对象有很多方法。但是,大都会选择Object.create方法而不是new关键字。这是有原因的,因为 使用Object.create方法时,可以将现有对象用作新创建的对象的原型。这样就可以重用现有对象的属性和功能,有点像OOP中的继承概念。

使用Object.assign方法时,可以将可枚举的自身属性从一个或多个源对象复制到目标对象。在这种情况下,目标对象的原型不包含源对象的属性。这是这两种方法之间的主要区别。

通过了解对象创建的这三种方式,可以根据实际情况适当地使用它们,以创建效率更高的程序。

19.map,filter, reduce 方法

当涉及到数组操作时,这三种方法非常有用。它们可以在Array原型中找到。

如果你有一个数组,并且想对每个元素做一些事情,那么您可以使用map方法。

如果你有一个数组,并且想通过某些条件来过滤一些值时,则可以使用filter方法。

reduce() 方法对数组中的每个元素执行一个由你提供的reducer函数(升序执行),将其结果汇总为单个返回值。

典型的例子就是对数组的所有元素进行求和:

let numbers = [1,2,3,4,5,6]
const reduced = numbers.reduce( (accumulator, currentValue) => accumulator + currentValue )
console.log(reduced)
// 21

请注意,上述三种方法不会更改原始数组的值。

20.纯函数,副作用和状态变更

这三个概念对于 JS 开发人员而言非常重要,状态变更对于使用 React 的开发人员尤其重要。

纯函数指的是一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用。

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情,比如:

  • 修改了一个变量
  • 直接修改数据结构
  • 设置一个对象的成员
  • 抛出一个异常或以一个错误终止
  • 打印到终端或读取用户输入
  • 读取或写入一个文件
  • 在屏幕上画图

状态变更是指你更改变量值的地方。如果你对变量进行更改,则可能会影响到其他函数,具体取决于变量被更改之前的值。在React环境中,建议我不要改变状态。

21. 闭包

闭包很难理解。但是一旦理解,你会觉得 JS 其实也挺好的。在线上有足够的资源。你花足够的时间学习闭包,掌握理解它并不难。

使用闭包可以访问内部作用域中外部作用域的作用域。每次创建函数时都会在函数创建时创建JavaScript闭包。

22. 高阶函数

高阶函数是将其他函数作为参数或返回结果的函数。你可以创建仅负责一项任务的较小函数,然后在这些较小函数的帮助下构造复杂函数。这也会提交代码的可重用性。

23.递归

递归是所有编程语言中的一个常见概念。简单地说,递归就是把大问题分解成小问题,然后解决小问题一种思路。

尽管递归可能是一个让你头疼的令人困惑的概念,但是通过大量的练习,从一些小问题开始,你可以更好地理解它。

24.集合与生成器

ES6 中新引入了集合和生成器。新引入的集合有MapSetWeakSet和WeakMap。这些集合为我们提供一些很方便的操作。了解它们的方式至关重要,尤其是对于现代JavaScript

生成器有时很难理解,特别是对于初学者。生成器允许我们编写代码函数,从而能够暂停和重新启动函数,而不会阻止其他代码的执行,这在JavaScript中是很不常见的。

25. Promise

JecelynPromises 的解释如下:“想象一下你是个孩子。你妈妈向你保证,她下周会买一部新手机给你。”

你要到下周才能知道你是否能屋那部手机。你的妈妈要么真的给你买了一个全新的手机,要么因为不开心就不给你买。

这算是一个承诺。一个 Promise 有三个状态,分别是:

  1. Pending:你不知道你是否会能得到那个电话
  2. Fulfilled:妈妈高兴了,给你买了一部新手机
  3. Rejected:老妈不开心了,就是不给买,爱咋滴就咋滳

26.异步编程

要了解什么是异步编程,首先要先积善成德什么是同步编程。同步编程是线程阻塞的,由于 JS 是单线程的,因此代码将逐行执行。

但是使用异步代码,你可以执行一些比较耗时的任务。当你必须执行花费很长时间才能完成的多个任务时,此功能特别有用。但是在某些情况下,即使是需要执行很长时间的代码,也可能需要用同步的方式,这时就可以使用async/await

27. ES6 箭头函数

箭头函数是 ES6 的新增功能,是常规函数的语法替代。区别在于箭头函数不绑定到thisargumentssupernew.target关键字。这使得箭头函数在某些情况下是一个不错的选择,而在另一些情况下则是一个非常糟糕的选择。

因此,不要一上来就使用箭头函数。需要根据你实际情况还使用它们。

28. 数据结构

无论使用哪种编程语言,数据结构都是开发人员应具备的基本知识之一。

糟糕的程序员担心代码,好的程序员担心数据结构和它们之间的关系。

数据结构方面,你应该了解链表,队列,堆栈,树,图和哈希表。

29.时间复杂度

不管编程语言如何,时间复杂度分析也是计算机编程的另一个基础。为了构建更好的应用程序,你应该编写更好的解决方案。为此,你需要了解时间复杂度的概念。有时也称为BigO

30.算法

这也是在计算机基础课程中首先要教的内容之一。简而言之,算法是逐步实现目标的过程。程序员应该能够从算法的角度看任何问题。

尽管有成千上万个用例的大量算法,但是下面两个很常见:

  • 查找
  • 排序

这两个用例对程序员来说是非常常见的,至少应该了解实现它们的已知算法。没有固定的规则规定你应该使用这些算法之一,但是这些算法在性能方面是众所周知的,并且有很好的文档证明。

你甚至可以创建自己的算法,并将其介绍给世界。如果它比目前已知的算法更好,你可能会成为下一个编程明星。

31.继承,多态和代码重用

JS 中的继承可用于原型来实现。这是因为 JS 是非OOP语言。但是 JS 通过提供原型继承来提供OOP的某些功能。

多态是对象、变量或函数可以采用多种形式的概念。在 JS 中,要看到多态的效果有点困难,因为在静态类型的系统中,多态的经典类型更明显。

以上两个概念都可以帮助我们在 JS 中实现更好代码重用。

32.设计模式

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

33. 函数式编程

函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变。

你需要掌握函数式编程的几个概念:

  • 纯函数
  • 不可变
  • 引用透明性
  • 高阶函数

34. 简洁代码的原则

无论使用哪种编程语言,这都是每个开发人员都应该掌握的一项基本技能。每种编程语言都有一套单独的良好实践。尽管这些“良好”做法是主观的,并且在工作场所之间存在差异,但有些惯例被认为是“良好”。

通过遵循这些代码原则,可以确保每个人都可以阅读和维护你的代码。这也会帮助你和你的团队在应用程序开发过程中顺利合作。

35. 解构赋值

ES6中引入了解构赋值操作符,它非常有用。对于相同的用例,它们比以前的实现更简单、更有效。

36. ES2020新特性

编程的优点之一是,如果你不去不断学习,你永远不会成为该领域专家。编程语言会随着时间不断发展,因为每个主要版本中都引入了其他新的功能。

这也说明了你对某个概念的专业知识很可能在将来的10年后会过期,因为会有更好的替代版本与版本更新一起发布。对于任何编程语言,这都是非常常见的情况。

ES2020发布了几个新特性,包括可选链接、空值合并、动态导入等等。你必须学习这些新概念,以跟上快速变化的It世界。

掌握一门语言需要多年的经验和时间,但是知道要掌握什么会让事情变得更容易,希望这36个概念能对你有所帮助。

文章来源:medium.com/better-programming/36-javascript-concepts-you-need-to-master-to-become-an-expert-c6630ac41bf4

JavaWeb 中 Service 层异常是直接处理还是抛到Controller 层处理

thbcm阅读(194)

JavaWebService 层异常会怎么处理?这是个非常有启发意义的问题。

一般初学者学习编码和错误处理时,先知道编程语言有一种处理错误的形式或者约定(如Java就是抛异常),然后就开始用这些工具,但是却反过来忽视这个问题的本质:

处理错误是为了写出正确的程序

到底怎么算“正确”呢?是要由解决的问题决定的。问题不同,解决方案就不同。

比如,一个web接口接受用户的请求,这个请求需要传入一个参数“年龄”,也许业务要求这个字段应该是个0~150之间的整数。如果用户输入的是个字符串或者负数就肯定不被接受。一般在后端的某个地方都会做输入的合法性检查,检查不过就抛异常。但是归根到底这个问题的“正确”解决方法总是要以某种形式提示用户。而提示用户是某种前端工作,这就要看这个界面到底是appH5 + ajax还是类似于jsp那样的服务器产生的界面。不管哪种,你需要根据需求去”设计一个修复错误“的流程。比如一个常见的流程需要后端抛异常,然后一路到某个集中处理错误的代码,将其转换为某个HTTP的错误(某个特定业务错误码)提供给前端,前端再去做”提示“。如果用户输入了非法的请求,从逻辑上后端都没法自己修复,这是个“正确”的策略。

换一个例子,比如用户想上传一个头像,后端将图片发给某个云存储,结果云存储报500错误。怎么办呢?你可能想到了重试几次,因为也许问题仅仅是临时的网络抖动而已,重试就可以正常执行。但如果重试多次无效。如果做系统时设计了某种热备方案,那么就可能改为发到另外一个服务器上。“重试”和“使用备份的依赖”都是“立刻处理“。

(推荐教程:Java教程

但如果重试无效,所有的备份服务也无效,那么也许就能像上面那样把错误抛给前端,提示用户“服务器开小差”。从这个方案很容易看出来,你想把错误抛到哪里是因为那个catch的地方是处理问题最方便的地方。一个问题的解决方案可能要几个不同的错误处理组合起来才能办到。

另外一个例子,你的程序抛了一个NPE。这一般就是程序员的bug——要不就是程序员想要表达一个东西”没有“,结果在后续处理中忘了判断是否为null;要不就是在写代码时觉得100%不可能为null的地方出现了一个null。不管哪种情况,这个错误用户总会看到一个很含糊的报错信息,这远远不够。“正确”的办法是程序员自己能尽快发现它,并尽快修复。要做到这一点,需要监控系统不断的爬log,把问题报警出来。而不是等到用户找客服来吐槽。

再换一个例子,比如你的后端程序突然OOM,挂了。挂的程序是没法恢复自己的。要做到“正确”就必须得在服务之外的容器考虑这个问题。比如你的服务跑在k8s上,他们会监控你程序的状态,然后重新启动新的服务实例以弥补挂掉的服务,还得调整流量,把去往挂掉服务的流量切掉,重新换到新的实例上。这里的恢复因为跨系统所以不能仅仅用异常实现,但是道理是一样的。但光靠重启就是“正确”的吗?如果服务是完全无状态的,问题不大。但是如果是有状态的,部分用户数据可能就会被执行一半的请求搞乱套。因此重启时要留意先“恢复数据到合法状态”。这又回到了你需要知道怎么样才是“正确”的做法。只依靠简单的语法功能是不能无脑解决这个事的。

  • 我们可以推广下,一个工作线程的“外部容器“是管理工作线程的“master”。一个网络请求的“外部容器”是一个web server。一个用户进程的“外部容器”是操作系统。Erlang把这种supervisor-worker的机制融入到语言的设计中。

(推荐微课:Java微课

Web程序之所以很大程度上能够把异常抛给顶层,主要由于3个原因:

  • 请求来自于前端,对于因为用户请求有误(数据合法性、权限、用户上下文状态)造成的问题,最终大概率只能告诉用户。因此抛异常到一个集中处理错误的地方,把异常转换为某个业务错误码的方法是合理的。
  • 后端服务一般都是无状态的。这也是互联网系统设计的一般性原则。无状态就意味着可以随意重启。对于用户的数据因为下一条一般情况下不会出问题。
  • 后端对数据的修改依赖DB的事务。因此一个改了一半的没提交的事务是不会造成副副作用。

但你要清楚上面这3条并不是总是成立的。总会存在一些处理逻辑并非完全无状态,也并不是所有的数据修改都能用一个事务保护。尤其要注意对微服务的调用,对内存状态的修改是没有事务保护的,一不留神就会出现搞乱用户数据的问题。比如:

 try {
   int res1 = doStep1();
   this.statusVar1 += res1;
   int res2 = doStep2();
   this.statusVar2 += res2;
   int res3 = doStep3(); // throw an exception
   this.statusVar3 = statusVar1 + statusVar2 + res3;
} catch ( ...) { 
   // ...
}

先假设this.statusVar1, this.statusVar2, this.statusVar3之间需要维护某种不变的约束(invariant)。然后执行这段代码时,如果在doStep3那抛出一个异常下面对statusVar3的赋值就不会执行。这时如果不能将statusVar1statusVar2的修改rollback回去,就会造成数据违反约束的问题。而程序员一般是很难直接发现这个数据被改坏了。而坏掉的数据可能会偷偷的导致其他依赖这个数据的代码逻辑出错(比如原本应该给积分的,结果却没给)。而这种错误一般非常难调查,从大量数据里找到不正确的那一小撮是相当困难的事。

比起上面这段更难搞得定的是这样的代码:

// controller
void controllerMethod(/* some params*/) {
  try {
    return svc.doWorkAndGetResult(/* some params*/);
  } catch (Exception e) {
    return ErrorJsonObject.of(e);
  }
}


// class svc
void doWorkAndGetResult(/* some params*/) {
    int res1 = otherSvc1.doStep1(/* some params */);
    this.statusVar1 += res1;
    int res2 = otherSvc2.doStep2(/* some params */);
    this.statusVar2 += res2;
    int res3 = otherSvc3.doStep3(/* some params */);
    this.statusVar3 = statusVar1 + statusVar2 + res3;
    return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);
}

这段代码的可怕之处在于,你在写的时候可能会以为doStep1~3这种东西即使抛异常,也能被Controller里的catch。在svc这层是不用处理任何异常的,因此不写try……catch是天经地义的。但实际上doStep1doStep2doStep3任何一个抛异常都会造成svc的数据状态不一致。甚至你一开始都可以通过文档或者其他沟通方式确定doStep1doStep2doStep3一开始都是必然可以成功,不会抛错的,因此你写的代码一开始是对的。但是你可能无法控制他们的实现(比如他们是另外一个团队开发的lib提供的),而他们的实现可能会改成会抛错。你的代码可能在完全不自知的情况下从“不会出问题”变成了“可能出问题”…… 更可怕的是类似于这样的代码是不能正确工作的:

void doWorkAndGetResult(/* some params*/) {
    try {
       int res1 = otherSvc1.doStep1(/* some params */);
       this.statusVar1 += res1;
       int res2 = otherSvc2.doStep2(/* some params */);
       this.statusVar2 += res2;
       int res3 = otherSvc3.doStep3(/* some params */);
       this.statusVar3 = statusVar1 + statusVar2 + res3;
       return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);
   } catch (Exception e) {
     // do rollback
   }
}

你可能以为这样就会处理好数据rollback了,甚至你会觉得这种代码非常优雅。但是实际上doStep1~3每一个地方抛错,rollback的代码都不一样。你必须得这么写:

void doWorkAndGetResult(/* some params*/) {
    int res1, res2, res3;
    try {
       res1 = otherSvc1.doStep1(/* some params */);
       this.statusVar1 += res1;
    } catch (Exception e) {
       throw e;
    }


    try {
      res2 = otherSvc2.doStep2(/* some params */);
      this.statusVar2 += res2;
    } catch (Exception e) {
      // rollback statusVar1
      this.statusVar1 -= res1;
      throw e;
    }

  
    try {
      res3 = otherSvc3.doStep3(/* some params */);
      this.statusVar3 = statusVar1 + statusVar2 + res3;
    } catch (Exception e) {
      // rollback statusVar1 & statusVar2
      this.statusVar1 -= res1;
      this.statusVar2 -= res2;
      throw e;
   } 
}

这才是能得到正确结果的代码——在任何地方出现错误都能维护数据一致性。优雅吗?看起来很丑。这甚至比goif err != nil还丑。但如果一定要在正确性和优雅性上作出取舍,我会毫不犹豫的选择前者。作为程序员是不能直接认为抛异常可以解决任何问题的,你必须学会写出有正确逻辑的程序,哪怕很难,并且看起来很丑。为了达成很高的正确性,你不能总是把自己大部分注意力放在“一切都OK的流程上“,而把错误看作是可以随便搞一下的工作,或者简单的相信exception可以自动搞定一切。

总结一下,我希望所有程序员对错误处理都要有起码的敬畏之心。Java这边因为Checked Exception的设计问题不得不避免使用(见大宽宽 – Java设计出checked exception有必要吗?),而Uncaughted Exception实在是太过于弱鸡,是不能给程序员提供更好地帮助的。

因此,程序员在每次抛错或者处理错误的时候都要对自己灵魂三击:这个错误的处理是正确的吗?会让用户看到什么?会不会搞乱数据?不要以为自己抛了个异常就不管了。

此外,在编译器不能帮上太多忙的时候,好好写UT来保护代码脆弱的正确性。

以上就是w3cschool分享的JavaWeb 中 Service 层异常抛到 Controller 层处理还是直接处理。希望对大家有所帮助。

带你认识开源在线教学软件:BigBlueButton

thbcm阅读(218)

BigBlueButton 是一个为在线教学量身定制的开源视频会议工具。让我们来看看它提供了什么。

在 2020 年,在家远程工作是一种新常态。当然,你不可能远程完成所有事情,但是在线教学是可以的。

尽管许多老师和学校组织不熟悉那些所有的出色工具,但某些 最好的开源视频会议工具 在一定程度上满足了要求。

在我提到的视频通话软件中, BigBlueButton 引起了我的注意。在这里,我将为你简单介绍。

BigBlueButton:用于在线教学的开源 Web 会议系统

BigBlueButton 是一个开源的网络会议方案,它旨在简化在线学习。

它是完全免费的,但是需要你在自己的服务器上安装才能将其用作成熟的在线学习解决方案。

BigBlueButton 提供了非常好的一组功能。你可以轻松地尝试 演示实例 ,并在学校的服务器上进行安装。

(推荐教程:Linux教程

开始之前,请先了解以下功能:

BigBlueButton 的功能

BigBlueButton 为教师和学校的量身定制了一系列在线课堂的有用功能,你可以获得:

  • 现场白板
  • 给公共和私人发消息
  • 支持网络摄像头
  • 支持会话记录
  • 支持表情符号
  • 能够将用户分组以进行团队协作
  • 支持投票
  • 屏幕共享
  • 支持多用户白板
  • 能够自行托管
  • 提供用于轻松集成到 Web 应用中的 API

除了提供的功能外,你还能看到易于使用的 UI,即 Greenlight (BigBlueButton 的前端界面),当你在服务器上配置时可以安装它。

(推荐微课:Linux微课

你可以尝试演示实例来临时免费地教你的学生。但是,考虑到使用 演示实例 来尝试 BigBlueButton 的局限性(限制为 60 分钟),建议你将它托管在服务器上,以探索其提供的所有功能。

(推荐课程:Linux就该这么学

为了更清楚地了解这些功能是如何工作的,你可能需要看下它的 官方教程

在你的服务器上安装 BigBlueButton

他们提供了详细文档,它对每个开发人员都会有用。安装它最简单、最快捷的方法是使用 bbb-install 脚本 ,但是如果不成功,你也可以探索其他选项。

对于刚接触的人,你需要一台至少运行 Ubuntu 16.04 LTS的服务器。在为 BigBlueButton 部署服务器之前,你应该查看最低要求 。

你可以在它的 GitHub 页面 中进一步了解该项目。

如果你正在为在线教学寻求解决方案,那么BigBlueButton是一个不错的选择。

它可能不提供原生的智能手机应用,但你肯定可以用手机上的网络浏览器来访问它。当然,最好找一台笔记本电脑/计算机来访问在线教学平台,但它也可以在移动设备上使用。

以上就是关于开源在线教学软件:BigBlueButton的介绍,希望对大家有所帮助。

联系我们