分类 软件开发 下的文章

我来介绍一下我是如何使用 Python 来节省成本的。

我最近在开一辆烧 93 号汽油的车子。根据汽车制造商的说法,它只需要加 91 号汽油就可以了。然而,在美国只能买到 87 号、89 号、93 号汽油。而我家附近的汽油的物价水平是每增加一号,每加仑就要多付 30 美分,因此如果加 93 号汽油,每加仑就要多花 60 美分。为什么不能节省一些钱呢?

一开始很简单,只需要先加满 93 号汽油,然后在油量表显示油箱半满的时候,用 89 号汽油加满,就得到一整箱 91 号汽油了。但接下来就麻烦了,剩下半箱 91 号汽油加上半箱 93 号汽油,只会变成一箱 92 号汽油,再接下来呢?如果继续算下去,只会越来越混乱。这个时候 Python 就派上用场了。

我的方案是,可以根据汽油的实时状态,不断向油箱中加入 93 号汽油或者 89 号汽油,而最终目标是使油箱内汽油的号数不低于 91。我需要做的是只是通过一些算法来判断新旧汽油混合之后的号数。使用多项式方程或许也可以解决这个问题,但如果使用 Python,好像只需要进行循环就可以了。

#!/usr/bin/env python
# octane.py

o = 93.0
newgas = 93.0   # 这个变量记录上一次加入的汽油号数
i = 1
while i < 21:                   # 20 次迭代 (加油次数)
    if newgas == 89.0:          # 如果上一次加的是 89 号汽油,改加 93 号汽油
        newgas = 93.0
        o = newgas/2 + o/2      # 当油箱半满的时候就加油
    else:                       # 如果上一次加的是 93 号汽油,则改加 89 号汽油
        newgas = 89.0
        o = newgas/2 + o/2      # 当油箱半满的时候就加油
    print str(i) + ': '+ str(o)
    i += 1

在代码中,我首先将变量 o(油箱中的当前混合汽油号数)和变量 newgas(上一次加入的汽油号数)的初始值都设为 93,然后循环 20 次,也就是分别加入 89 号汽油和 93 号汽油一共 20 次,以保持混合汽油号数稳定。

1: 91.0
2: 92.0
3: 90.5
4: 91.75
5: 90.375
6: 91.6875
7: 90.34375
8: 91.671875
9: 90.3359375
10: 91.66796875
11: 90.333984375
12: 91.6669921875
13: 90.3334960938
14: 91.6667480469
15: 90.3333740234
16: 91.6666870117
17: 90.3333435059
18: 91.6666717529
19: 90.3333358765
20: 91.6666679382

从以上数据来看,只需要 10 到 15 次循环,汽油号数就比较稳定了,也相当接近 91 号汽油的目标。这种交替混合直到稳定的现象看起来很有趣,每次交替加入同等量的不同号数汽油,都会趋于稳定。实际上,即使加入的 89 号汽油和 93 号汽油的量不同,也会趋于稳定。

因此,我尝试了不同的比例,我认为加入的 93 号汽油需要比 89 号汽油更多一点。在尽量少补充新汽油的情况下,我最终计算到的结果是 89 号汽油要在油箱大约 7/12 满的时候加进去,而 93 号汽油则要在油箱 ¼ 满的时候才加进去。

我的循环将会更改成这样:

    if newgas == 89.0:            
                                 
        newgas = 93.0
        o = 3*newgas/4 + o/4      
    else:                        
        newgas = 89.0
        o = 5*newgas/12 + 7*o/12

以下是从第十次加油开始的混合汽油号数:

10: 92.5122272978
11: 91.0487992571
12: 92.5121998143
13: 91.048783225
14: 92.5121958062
15: 91.048780887

如你所见,这个调整会令混合汽油号数始终略高于 91。当然,我的油量表并没有 1/12 的刻度,但是 7/12 略小于 5/8,我可以近似地计算。

一个更简单地方案是每次都首先加满 93 号汽油,然后在油箱半满时加入 89 号汽油直到耗尽,这可能会是我的常规方案。就我个人而言,这种方法并不太好,有时甚至会产生一些麻烦。但对于长途旅行来说,这种方案会相对简便一些。有时我也会因为油价突然下跌而购买一些汽油,所以,这个方案是我可以考虑的一系列选项之一。

当然最重要的是:开车不写码,写码不开车!


via: https://opensource.com/article/18/10/python-gas-pump

作者:Greg Pittman 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用 Python behave 框架的行为驱动开发模式可以帮助你的团队更好的协作和测试自动化。

您是否听说过 行为驱动开发 behavior-driven development (BDD),并好奇这是个什么东西?也许你发现了团队成员在谈论“嫩瓜”(LCTT 译注:“ 嫩瓜 gherkin ” 是一种简单的英语文本语言,工具 cucumber 通过解释它来执行测试脚本,见下文),而你却不知所云。或许你是一个 Python 人 Pythonista ,正在寻找更好的方法来测试你的代码。 无论在什么情况下,了解 BDD 都可以帮助您和您的团队实现更好的协作和测试自动化,而 Python 的 behave 框架是一个很好的起点。

什么是 BDD?

在软件中,行为是指在明确定义的输入、动作和结果场景中功能是如何运转的。 产品可以表现出无数的行为,例如:

  • 在网站上提交表单
  • 搜索想要的结果
  • 保存文档
  • 进行 REST API 调用
  • 运行命令行界面命令

根据产品的行为定义产品的功能可以更容易地描述产品,并对其进行开发和测试。 BDD 的核心是:使行为成为软件开发的焦点。在开发早期使用示例语言的规范来定义行为。最常见的行为规范语言之一是 Gherkin,Cucumber项目中的Given-When-Then场景格式。 行为规范基本上是对行为如何工作的简单语言描述,具有一致性和焦点的一些正式结构。 通过将步骤文本“粘合”到代码实现,测试框架可以轻松地自动化这些行为规范。

下面是用Gherkin编写的行为规范的示例:

根据产品的行为定义产品的功能可以更容易地描述产品,开发产品并对其进行测试。 这是BDD的核心:使行为成为软件开发的焦点。 在开发早期使用示例规范的语言来定义行为。 最常见的行为规范语言之一是Gherkin,来自 Cucumber 项目中的 Given-When-Then 场景格式。 行为规范基本上是对行为如何工作的简单语言描述,具有一致性和聚焦点的一些正式结构。 通过将步骤文本“粘合”到代码实现,测试框架可以轻松地自动化这些行为规范。

下面是用 Gherkin 编写的行为规范的示例:

Scenario: Basic DuckDuckGo Search
  Given the DuckDuckGo home page is displayed
  When the user searches for "panda"
  Then results are shown for "panda"

快速浏览一下,行为是直观易懂的。 除少数关键字外,该语言为自由格式。 场景简洁而有意义。 一个真实的例子说明了这种行为。 步骤以声明的方式表明应该发生什么——而不会陷入如何如何的细节中。

BDD 的主要优点是良好的协作和自动化。 每个人都可以为行为开发做出贡献,而不仅仅是程序员。从流程开始就定义并理解预期的行为。测试可以与它们涵盖的功能一起自动化。每个测试都包含一个单一的、独特的行为,以避免重复。最后,现有的步骤可以通过新的行为规范重用,从而产生雪球效果。

Python 的 behave 框架

behave 是 Python 中最流行的 BDD 框架之一。 它与其他基于 Gherkin 的 Cucumber 框架非常相似,尽管没有得到官方的 Cucumber 定名。 behave 有两个主要层:

  1. 用 Gherkin 的 .feature 文件编写的行为规范
  2. 用 Python 模块编写的步骤定义和钩子,用于实现 Gherkin 步骤

如上例所示,Gherkin 场景有三部分格式:

  1. 鉴于(Given)一些初始状态
  2. 每当(When)行为发生时
  3. 然后(Then)验证结果

当 behave 运行测试时,每个步骤由装饰器“粘合”到 Python 函数。

安装

作为先决条件,请确保在你的计算机上安装了 Python 和 pip。 我强烈建议使用 Python 3.(我还建议使用 pipenv,但以下示例命令使用更基本的 pip。)

behave 框架只需要一个包:

pip install behave

其他包也可能有用,例如:

pip install requests    # 用于调用 REST API
pip install selenium    # 用于 web 浏览器交互

GitHub 上的 behavior-driven-Python 项目包含本文中使用的示例。

Gherkin 特点

behave 框架使用的 Gherkin 语法实际上是符合官方的 Cucumber Gherkin 标准的。.feature 文件包含了功能(Feature)部分,而场景部分又包含具有 Given-When-Then 步骤的场景(Scenario) 部分。 以下是一个例子:

Feature: Cucumber Basket
  As a gardener,
  I want to carry many cucumbers in a basket,
  So that I don’t drop them all.

  @cucumber-basket
  Scenario: Add and remove cucumbers
    Given the basket is empty
    When "4" cucumbers are added to the basket
    And "6" more cucumbers are added to the basket
    But "3" cucumbers are removed from the basket
    Then the basket contains "7" cucumbers

这里有一些重要的事情需要注意:

  • FeatureScenario 部分都有简短的描述性标题
  • 紧跟在 Feature 标题后面的行是会被 behave 框架忽略掉的注释。将功能描述放在那里是一种很好的做法。
  • ScenarioFeature 可以有标签(注意 @cucumber-basket 标记)用于钩子和过滤(如下所述)。
  • 步骤都遵循严格的 Given-When-Then 顺序
  • 使用 AndBut 可以为任何类型添加附加步骤。
  • 可以使用输入对步骤进行参数化——注意双引号里的值。

通过使用场景大纲(Scenario Outline),场景也可以写为具有多个输入组合的模板:

Feature: Cucumber Basket

  @cucumber-basket
  Scenario Outline: Add cucumbers
    Given the basket has “<initial>” cucumbers
    When "<more>" cucumbers are added to the basket
    Then the basket contains "<total>" cucumbers

    Examples: Cucumber Counts
      | initial | more | total |
      |    0    |   1  |   1   |
      |    1    |   2  |   3   |
      |    5    |   4  |   9   |

场景大纲总是有一个示例(Examples)表,其中第一行给出列标题,后续每一行给出一个输入组合。 只要列标题出现在由尖括号括起的步骤中,行值就会被替换。 在上面的示例中,场景将运行三次,因为有三行输入组合。 场景大纲是避免重复场景的好方法。

Gherkin 语言还有其他元素,但这些是主要的机制。 想了解更多信息,请阅读 Automation Panda 这个网站的文章 Gherkin by ExampleWriting Good Gherkin

Python 机制

每个 Gherkin 步骤必须“粘合”到步骤定义——即提供了实现的 Python 函数。 每个函数都有一个带有匹配字符串的步骤类型装饰器。它还接收共享的上下文和任何步骤参数。功能文件必须放在名为 features/ 的目录中,而步骤定义模块必须放在名为 features/steps/ 的目录中。 任何功能文件都可以使用任何模块中的步骤定义——它们不需要具有相同的名称。 下面是一个示例 Python 模块,其中包含 cucumber basket 功能的步骤定义。

from behave import *
from cucumbers.basket import CucumberBasket

@given('the basket has "{initial:d}" cucumbers')
def step_impl(context, initial):
    context.basket = CucumberBasket(initial_count=initial)

@when('"{some:d}" cucumbers are added to the basket')
def step_impl(context, some):
    context.basket.add(some)

@then('the basket contains "{total:d}" cucumbers')
def step_impl(context, total):
    assert context.basket.count == total

可以使用三个步骤匹配器parsecfparsere。默认的,也是最简单的匹配器是 parse,如上例所示。注意如何解析参数化值并将其作为输入参数传递给函数。一个常见的最佳实践是在步骤中给参数加双引号。

每个步骤定义函数还接收一个上下文变量,该变量保存当前正在运行的场景的数据,例如 featurescenariotags 字段。也可以添加自定义字段,用于在步骤之间共享数据。始终使用上下文来共享数据——永远不要使用全局变量!

behave 框架还支持钩子来处理 Gherkin 步骤之外的自动化问题。钩子是一个将在步骤、场景、功能或整个测试套件之前或之后运行的功能。钩子让人联想到面向方面的编程。它们应放在 features/ 目录下的特殊 environment.py 文件中。钩子函数也可以检查当前场景的标签,因此可以有选择地应用逻辑。下面的示例显示了如何使用钩子为标记为 @web 的任何场景生成和销毁一个 Selenium WebDriver 实例。

from selenium import webdriver

def before_scenario(context, scenario):
    if 'web' in context.tags:
        context.browser = webdriver.Firefox()
        context.browser.implicitly_wait(10)

def after_scenario(context, scenario):
    if 'web' in context.tags:
        context.browser.quit()

注意:也可以使用 fixtures 进行构建和清理。

要了解一个 behave 项目应该是什么样子,这里是示例项目的目录结构:

任何 Python 包和自定义模块都可以与 behave 框架一起使用。 使用良好的设计模式构建可扩展的测试自动化解决方案。步骤定义代码应简明扼要。

运行测试

要从命令行运行测试,请切换到项目的根目录并运行 behave 命令。 使用 -help 选项查看所有可用选项。

以下是一些常见用例:

# run all tests
behave

# run the scenarios in a feature file
behave features/web.feature

# run all tests that have the @duckduckgo tag
behave --tags @duckduckgo

# run all tests that do not have the @unit tag
behave --tags ~@unit

# run all tests that have @basket and either @add or @remove
behave --tags @basket --tags @add,@remove

为方便起见,选项可以保存在 config 文件中。

其他选择

behave 不是 Python 中唯一的 BDD 测试框架。其他好的框架包括:

  • pytest-bdd,是 pytest 的插件,和 behave 一样,它使用 Gherkin 功能文件和步骤定义模块,但它也利用了 pytest 的所有功能和插件。例如,它可以使用 pytest-xdist 并行运行 Gherkin 场景。 BDD 和非 BDD 测试也可以与相同的过滤器一起执行。pytest-bdd 还提供更灵活的目录布局。
  • radish 是一个 “Gherkin 增强版”框架——它将场景循环和前提条件添加到标准的 Gherkin 语言中,这使得它对程序员更友好。它还像 behave 一样提供了丰富的命令行选项。
  • lettuce 是一种较旧的 BDD 框架,与 behave 非常相似,在框架机制方面存在细微差别。然而,GitHub 最近显示该项目的活动很少(截至2018 年 5 月)。

任何这些框架都是不错的选择。

另外,请记住,Python 测试框架可用于任何黑盒测试,即使对于非 Python 产品也是如此! BDD 框架非常适合 Web 和服务测试,因为它们的测试是声明性的,而 Python 是一种很好的测试自动化语言

本文基于作者的 PyCon Cleveland 2018 演讲“行为驱动的Python”。


via: https://opensource.com/article/18/5/behavior-driven-python

作者:Andrew Knight 选题:lujun9972 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

灵活多样的 Python 库为数据分析和数据挖掘提供了强力的机器学习工具。

Scikit-learn Python 库最初于 2007 年发布,通常用于解决各种方面的机器学习和数据科学问题。这个多种功能的库提供了整洁、一致、高效的 API 和全面的在线文档。

什么是 Scikit-learn?

Scikit-learn 是一个开源 Python 库,拥有强大的数据分析和数据挖掘工具。 在 BSD 许可下可用,并建立在以下机器学习库上:

  • NumPy,一个用于操作多维数组和矩阵的库。它还具有广泛的数学函数汇集,可用于执行各种计算。
  • SciPy,一个由各种库组成的生态系统,用于完成技术计算任务。
  • Matplotlib,一个用于绘制各种图表和图形的库。

Scikit-learn 提供了广泛的内置算法,可以充分用于数据科学项目。

以下是使用 Scikit-learn 库的主要方法。

1、分类

分类工具识别与提供的数据相关联的类别。例如,它们可用于将电子邮件分类为垃圾邮件或非垃圾邮件。

Scikit-learn 中的分类算法包括:

  • 支持向量机 Support vector machines (SVM)
  • 最邻近 Nearest neighbors
  • 随机森林 Random forest

2、回归

回归涉及到创建一个模型去试图理解输入和输出数据之间的关系。例如,回归工具可用于理解股票价格的行为。

回归算法包括:

  • 支持向量机 Support vector machines (SVM)
  • 岭回归 Ridge regression
  • Lasso(LCTT 译注:Lasso 即 least absolute shrinkage and selection operator,又译为最小绝对值收敛和选择算子、套索算法)

3、聚类

Scikit-learn 聚类工具用于自动将具有相同特征的数据分组。 例如,可以根据客户数据的地点对客户数据进行细分。

聚类算法包括:

  • K-means
  • 谱聚类 Spectral clustering
  • Mean-shift

4、降维

降维降低了用于分析的随机变量的数量。例如,为了提高可视化效率,可能不会考虑外围数据。

降维算法包括:

  • 主成分分析 Principal component analysis (PCA)
  • 功能选择 Feature selection
  • 非负矩阵分解 Non-negative matrix factorization

5、模型选择

模型选择算法提供了用于比较、验证和选择要在数据科学项目中使用的最佳参数和模型的工具。

通过参数调整能够增强精度的模型选择模块包括:

  • 网格搜索 Grid search
  • 交叉验证 Cross-validation
  • 指标 Metrics

6、预处理

Scikit-learn 预处理工具在数据分析期间的特征提取和规范化中非常重要。 例如,您可以使用这些工具转换输入数据(如文本)并在分析中应用其特征。

预处理模块包括:

  • 预处理
  • 特征提取

Scikit-learn 库示例

让我们用一个简单的例子来说明如何在数据科学项目中使用 Scikit-learn 库。

我们将使用鸢尾花花卉数据集,该数据集包含在 Scikit-learn 库中。 鸢尾花数据集包含有关三种花种的 150 个细节,三种花种分别为:

  • Setosa:标记为 0
  • Versicolor:标记为 1
  • Virginica:标记为 2

数据集包括每种花种的以下特征(以厘米为单位):

  • 萼片长度
  • 萼片宽度
  • 花瓣长度
  • 花瓣宽度

第 1 步:导入库

由于鸢尾花花卉数据集包含在 Scikit-learn 数据科学库中,我们可以将其加载到我们的工作区中,如下所示:

from sklearn import datasets
iris = datasets.load_iris()

这些命令从 sklearn 导入数据集 datasets 模块,然后使用 datasets 中的 load_iris() 方法将数据包含在工作空间中。

第 2 步:获取数据集特征

数据集 datasets 模块包含几种方法,使您更容易熟悉处理数据。

在 Scikit-learn 中,数据集指的是类似字典的对象,其中包含有关数据的所有详细信息。 使用 .data 键存储数据,该数据列是一个数组列表。

例如,我们可以利用 iris.data 输出有关鸢尾花花卉数据集的信息。

print(iris.data)

这是输出(结果已被截断):

[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]
 [5.4 3.9 1.7 0.4]
 [4.6 3.4 1.4 0.3]
 [5.  3.4 1.5 0.2]
 [4.4 2.9 1.4 0.2]
 [4.9 3.1 1.5 0.1]
 [5.4 3.7 1.5 0.2]
 [4.8 3.4 1.6 0.2]
 [4.8 3.  1.4 0.1]
 [4.3 3.  1.1 0.1]
 [5.8 4.  1.2 0.2]
 [5.7 4.4 1.5 0.4]
 [5.4 3.9 1.3 0.4]
 [5.1 3.5 1.4 0.3]

我们还使用 iris.target 向我们提供有关花朵不同标签的信息。

print(iris.target)

这是输出:

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]

如果我们使用 iris.target_names,我们将输出数据集中找到的标签名称的数组。

print(iris.target_names)

以下是运行 Python 代码后的结果:

['setosa' 'versicolor' 'virginica']

第 3 步:可视化数据集

我们可以使用箱形图来生成鸢尾花数据集的视觉描绘。 箱形图说明了数据如何通过四分位数在平面上分布的。

以下是如何实现这一目标:

import seaborn as sns
box_data = iris.data  # 表示数据数组的变量
box_target = iris.target  # 表示标签数组的变量
sns.boxplot(data = box_data,width=0.5,fliersize=5)
sns.set(rc={'figure.figsize':(2,15)})

让我们看看结果:

在横轴上:

  • 0 是萼片长度
  • 1 是萼片宽度
  • 2 是花瓣长度
  • 3 是花瓣宽度

垂直轴的尺寸以厘米为单位。

总结

以下是这个简单的 Scikit-learn 数据科学教程的完整代码。

from sklearn import datasets
iris = datasets.load_iris()
print(iris.data)
print(iris.target)
print(iris.target_names)
import seaborn as sns
box_data = iris.data  # 表示数据数组的变量
box_target = iris.target  # 表示标签数组的变量
sns.boxplot(data = box_data,width=0.5,fliersize=5)
sns.set(rc={'figure.figsize':(2,15)})

Scikit-learn 是一个多功能的 Python 库,可用于高效完成数据科学项目。

如果您想了解更多信息,请查看 LiveEdu 上的教程,例如 Andrey Bulezyuk 关于使用 Scikit-learn 库创建机器学习应用程序的视频。

有什么评价或者疑问吗? 欢迎在下面分享。


via: https://opensource.com/article/18/9/how-use-scikit-learn-data-science-projects

作者:Dr.Michael J.Garbade 选题:lujun9972 译者:Flowsnow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Anthony Starks 使用他出色的 Deck 演示工具重构了我原来的基于 Google Slides 的幻灯片。你可以在他的博客上查看他重构后的幻灯片,
mindchunk.blogspot.com.au/2014/06/remixing-with-deck

我最近被邀请在 Gocon 发表演讲,这是一个每半年在日本东京举行的 Go 的精彩大会。Gocon 2014 是一个完全由社区驱动的为期一天的活动,由培训和一整个下午的围绕着生产环境中的 Go 这个主题的演讲组成.(LCTT 译注:本文发表于 2014 年)

以下是我的讲义。原文的结构能让我缓慢而清晰的演讲,因此我已经编辑了它使其更可读。

我要感谢 Bill Kennedy 和 Minux Ma,特别是 Josh Bleecher Snyder,感谢他们在我准备这次演讲中的帮助。


大家下午好。

我叫 David.

我很高兴今天能来到 Gocon。我想参加这个会议已经两年了,我很感谢主办方能提供给我向你们演讲的机会。

Gocon 2014

我想以一个问题开始我的演讲。

为什么选择 Go?

当大家讨论学习或在生产环境中使用 Go 的原因时,答案不一而足,但因为以下三个原因的最多。

Gocon 2014

这就是 TOP3 的原因。

第一,并发。

Go 的 并发原语 Concurrency Primitives 对于来自 Nodejs,Ruby 或 Python 等单线程脚本语言的程序员,或者来自 C++ 或 Java 等重量级线程模型的语言都很有吸引力。

易于部署。

我们今天从经验丰富的 Gophers 那里听说过,他们非常欣赏部署 Go 应用的简单性。

Gocon 2014

然后是性能。

我相信人们选择 Go 的一个重要原因是它 快。

Gocon 2014 (4)

在今天的演讲中,我想讨论五个有助于提高 Go 性能的特性。

我还将与大家分享 Go 如何实现这些特性的细节。

Gocon 2014 (5)

我要谈的第一个特性是 Go 对于值的高效处理和存储。

Gocon 2014 (6)

这是 Go 中一个值的例子。编译时,gocon 正好消耗四个字节的内存。

让我们将 Go 与其他一些语言进行比较

Gocon 2014 (7)

由于 Python 表示变量的方式的开销,使用 Python 存储相同的值会消耗六倍的内存。

Python 使用额外的内存来跟踪类型信息,进行 引用计数 Reference Counting 等。

让我们看另一个例子:

Gocon 2014 (8)

与 Go 类似,Java 消耗 4 个字节的内存来存储 int 型。

但是,要在像 ListMap 这样的集合中使用此值,编译器必须将其转换为 Integer 对象。

Gocon 2014 (9)

因此,Java 中的整数通常消耗 16 到 24 个字节的内存。

为什么这很重要? 内存便宜且充足,为什么这个开销很重要?

Gocon 2014 (10)

这是一张显示 CPU 时钟速度与内存总线速度的图表。

请注意 CPU 时钟速度和内存总线速度之间的差距如何继续扩大。

两者之间的差异实际上是 CPU 花费多少时间等待内存。

Gocon 2014 (11)

自 1960 年代后期以来,CPU 设计师已经意识到了这个问题。

他们的解决方案是一个缓存,一个更小、更快的内存区域,介入 CPU 和主存之间。

Gocon 2014 (12)

这是一个 Location 类型,它保存物体在三维空间中的位置。它是用 Go 编写的,因此每个 Location 只消耗 24 个字节的存储空间。

我们可以使用这种类型来构造一个容纳 1000 个 Location 的数组类型,它只消耗 24000 字节的内存。

在数组内部,Location 结构体是顺序存储的,而不是随机存储的 1000 个 Location 结构体的指针。

这很重要,因为现在所有 1000 个 Location 结构体都按顺序放在缓存中,紧密排列在一起。

Gocon 2014 (13)

Go 允许您创建紧凑的数据结构,避免不必要的填充字节。

紧凑的数据结构能更好地利用缓存。

更好的缓存利用率可带来更好的性能。

Gocon 2014 (14)

函数调用不是无开销的。

Gocon 2014 (15)

调用函数时会发生三件事。

创建一个新的 栈帧 Stack Frame ,并记录调用者的详细信息。

在函数调用期间可能被覆盖的任何寄存器都将保存到栈中。

处理器计算函数的地址并执行到该新地址的分支。

Gocon 2014 (16)

由于函数调用是非常常见的操作,因此 CPU 设计师一直在努力优化此过程,但他们无法消除开销。

函调固有开销,或重于泰山,或轻于鸿毛,这取决于函数做了什么。

减少函数调用开销的解决方案是 内联 Inlining

Gocon 2014 (17)

Go 编译器通过将函数体视为调用者的一部分来内联函数。

内联也有成本,它增加了二进制文件大小。

只有当调用开销与函数所做工作关联度的很大时内联才有意义,因此只有简单的函数才能用于内联。

复杂的函数通常不受调用它们的开销所支配,因此不会内联。

Gocon 2014 (18)

这个例子显示函数 Double 调用 util.Max

为了减少调用 util.Max 的开销,编译器可以将 util.Max 内联到 Double 中,就象这样

Gocon 2014 (19)

内联后不再调用 util.Max,但是 Double 的行为没有改变。

内联并不是 Go 独有的。几乎每种编译或及时编译的语言都执行此优化。但是 Go 的内联是如何实现的?

Go 实现非常简单。编译包时,会标记任何适合内联的小函数,然后照常编译。

然后函数的源代码和编译后版本都会被存储。

Gocon 2014 (20)

此幻灯片显示了 util.a 的内容。源代码已经过一些转换,以便编译器更容易快速处理。

当编译器编译 Double 时,它看到 util.Max 可内联的,并且 util.Max 的源代码是可用的。

就会替换原函数中的代码,而不是插入对 util.Max 的编译版本的调用。

拥有该函数的源代码可以实现其他优化。

Gocon 2014 (21)

在这个例子中,尽管函数 Test 总是返回 false,但 Expensive 在不执行它的情况下无法知道结果。

Test 被内联时,我们得到这样的东西。

Gocon 2014 (22)

编译器现在知道 Expensive 的代码无法访问。

这不仅节省了调用 Test 的成本,还节省了编译或运行任何现在无法访问的 Expensive 代码。

Go 编译器可以跨文件甚至跨包自动内联函数。还包括从标准库调用的可内联函数的代码。

Gocon 2014 (23)

强制垃圾回收 Mandatory Garbage Collection 使 Go 成为一种更简单,更安全的语言。

这并不意味着垃圾回收会使 Go 变慢,或者垃圾回收是程序速度的瓶颈。

这意味着在堆上分配的内存是有代价的。每次 GC 运行时都会花费 CPU 时间,直到释放内存为止。

Gocon 2014 (24)

然而,有另一个地方分配内存,那就是栈。

与 C 不同,它强制您选择是否将值通过 malloc 将其存储在堆上,还是通过在函数范围内声明将其储存在栈上;Go 实现了一个名为 逃逸分析 Escape Analysis 的优化。

Gocon 2014 (25)

逃逸分析决定了对一个值的任何引用是否会从被声明的函数中逃逸。

如果没有引用逃逸,则该值可以安全地存储在栈中。

存储在栈中的值不需要分配或释放。

让我们看一些例子

Gocon 2014 (26)

Sum 返回 1 到 100 的整数的和。这是一种相当不寻常的做法,但它说明了逃逸分析的工作原理。

因为切片 numbers 仅在 Sum 内引用,所以编译器将安排到栈上来存储的 100 个整数,而不是安排到堆上。

没有必要回收 numbers,它会在 Sum 返回时自动释放。

Gocon 2014 (27)

第二个例子也有点尬。在 CenterCursor 中,我们创建一个新的 Cursor 对象并在 c 中存储指向它的指针。

然后我们将 c 传递给 Center() 函数,它将 Cursor 移动到屏幕的中心。

最后我们打印出那个 ‘Cursor` 的 X 和 Y 坐标。

即使 cnew 函数分配了空间,它也不会存储在堆上,因为没有引用 c 的变量逃逸 CenterCursor 函数。

Gocon 2014 (28)

默认情况下,Go 的优化始终处于启用状态。可以使用 -gcflags = -m 开关查看编译器的逃逸分析和内联决策。

因为逃逸分析是在编译时执行的,而不是运行时,所以无论垃圾回收的效率如何,栈分配总是比堆分配快。

我将在本演讲的其余部分详细讨论栈。

Gocon 2014 (30)

Go 有 goroutine。 这是 Go 并发的基石。

我想退一步,探索 goroutine 的历史。

最初,计算机一次运行一个进程。在 60 年代,多进程或 分时 Time Sharing 的想法变得流行起来。

在分时系统中,操作系统必须通过保护当前进程的现场,然后恢复另一个进程的现场,不断地在这些进程之间切换 CPU 的注意力。

这称为 进程切换。

Gocon 2014 (29)

进程切换有三个主要开销。

首先,内核需要保护该进程的所有 CPU 寄存器的现场,然后恢复另一个进程的现场。

内核还需要将 CPU 的映射从虚拟内存刷新到物理内存,因为这些映射仅对当前进程有效。

最后是操作系统 上下文切换 Context Switch 的成本,以及 调度函数 Scheduler Function 选择占用 CPU 的下一个进程的开销。

Gocon 2014 (31)

现代处理器中有数量惊人的寄存器。我很难在一张幻灯片上排开它们,这可以让你知道保护和恢复它们需要多少时间。

由于进程切换可以在进程执行的任何时刻发生,因此操作系统需要存储所有寄存器的内容,因为它不知道当前正在使用哪些寄存器。

Gocon 2014 (32)

这导致了线程的出生,这些线程在概念上与进程相同,但共享相同的内存空间。

由于线程共享地址空间,因此它们比进程更轻,因此创建速度更快,切换速度更快。

Gocon 2014 (33)

Goroutine 升华了线程的思想。

Goroutine 是 协作式调度 Cooperative Scheduled <br/> 的,而不是依靠内核来调度。

当对 Go 运行时调度器 Runtime Scheduler 进行显式调用时,goroutine 之间的切换仅发生在明确定义的点上。

编译器知道正在使用的寄存器并自动保存它们。

Gocon 2014 (34)

虽然 goroutine 是协作式调度的,但运行时会为你处理。

Goroutine 可能会给禅让给其他协程时刻是:

  • 阻塞式通道发送和接收。
  • Go 声明,虽然不能保证会立即调度新的 goroutine。
  • 文件和网络操作式的阻塞式系统调用。
  • 在被垃圾回收循环停止后。

Gocon 2014 (35)

这个例子说明了上一张幻灯片中描述的一些调度点。

箭头所示的线程从左侧的 ReadFile 函数开始。遇到 os.Open,它在等待文件操作完成时阻塞线程,因此调度器将线程切换到右侧的 goroutine。

继续执行直到从通道 c 中读,并且此时 os.Open 调用已完成,因此调度器将线程切换回左侧并继续执行 file.Read 函数,然后又被文件 IO 阻塞。

调度器将线程切换回右侧以进行另一个通道操作,该操作在左侧运行期间已解锁,但在通道发送时再次阻塞。

最后,当 Read 操作完成并且数据可用时,线程切换回左侧。

Gocon 2014 (36)

这张幻灯片显示了低级语言描述的 runtime.Syscall 函数,它是 os 包中所有函数的基础。

只要你的代码调用操作系统,就会通过此函数。

entersyscall 的调用通知运行时该线程即将阻塞。

这允许运行时启动一个新线程,该线程将在当前线程被阻塞时为其他 goroutine 提供服务。

这导致每 Go 进程的操作系统线程相对较少,Go 运行时负责将可运行的 Goroutine 分配给空闲的操作系统线程。

Gocon 2014 (37)

在上一节中,我讨论了 goroutine 如何减少管理许多(有时是数十万个并发执行线程)的开销。

Goroutine故事还有另一面,那就是栈管理,它引导我进入我的最后一个话题。

Gocon 2014 (39)

这是一个进程的内存布局图。我们感兴趣的关键是堆和栈的位置。

传统上,在进程的地址空间内,堆位于内存的底部,位于程序(代码)的上方并向上增长。

栈位于虚拟地址空间的顶部,并向下增长。

Gocon 2014 (40)

因为堆和栈相互覆盖的结果会是灾难性的,操作系统通常会安排在栈和堆之间放置一个不可写内存区域,以确保如果它们发生碰撞,程序将中止。

这称为保护页,有效地限制了进程的栈大小,通常大约为几兆字节。

Gocon 2014 (41)

我们已经讨论过线程共享相同的地址空间,因此对于每个线程,它必须有自己的栈。

由于很难预测特定线程的栈需求,因此为每个线程的栈和保护页面保留了大量内存。

希望是这些区域永远不被使用,而且防护页永远不会被击中。

缺点是随着程序中线程数的增加,可用地址空间的数量会减少。

Gocon 2014 (42)

我们已经看到 Go 运行时将大量的 goroutine 调度到少量线程上,但那些 goroutines 的栈需求呢?

Go 编译器不使用保护页,而是在每个函数调用时插入一个检查,以检查是否有足够的栈来运行该函数。如果没有,运行时可以分配更多的栈空间。

由于这种检查,goroutines 初始栈可以做得更小,这反过来允许 Go 程序员将 goroutines 视为廉价资源。

Gocon 2014 (43)

这是一张显示了 Go 1.2 如何管理栈的幻灯片。

G 调用 H 时,没有足够的空间让 H 运行,所以运行时从堆中分配一个新的栈帧,然后在新的栈段上运行 H。当 H 返回时,栈区域返回到堆,然后返回到 G

Gocon 2014 (44)

这种管理栈的方法通常很好用,但对于某些类型的代码,通常是递归代码,它可能导致程序的内部循环跨越这些栈边界之一。

例如,在程序的内部循环中,函数 G 可以在循环中多次调用 H

每次都会导致栈拆分。 这被称为 热分裂 Hot Split 问题。

Gocon 2014 (45)

为了解决热分裂问题,Go 1.3 采用了一种新的栈管理方法。

如果 goroutine 的栈太小,则不会添加和删除其他栈段,而是分配新的更大的栈。

旧栈的内容被复制到新栈,然后 goroutine 使用新的更大的栈继续运行。

在第一次调用 H 之后,栈将足够大,对可用栈空间的检查将始终成功。

这解决了热分裂问题。

Gocon 2014 (46)

值,内联,逃逸分析,Goroutines 和分段/复制栈。

这些是我今天选择谈论的五个特性,但它们绝不是使 Go 成为快速的语言的唯一因素,就像人们引用他们学习 Go 的理由的三个原因一样。

这五个特性一样强大,它们不是孤立存在的。

例如,运行时将 goroutine 复用到线程上的方式在没有可扩展栈的情况下几乎没有效率。

内联通过将较小的函数组合成较大的函数来降低栈大小检查的成本。

逃逸分析通过自动将从实例从堆移动到栈来减少垃圾回收器的压力。

逃逸分析还提供了更好的 缓存局部性 Cache Locality

如果没有可增长的栈,逃逸分析可能会对栈施加太大的压力。

Gocon 2014 (47)

  • 感谢 Gocon 主办方允许我今天发言
  • twitter / web / email details
  • 感谢 @offbymany,@billkennedy\_go 和 Minux 在准备这个演讲的过程中所提供的帮助。

相关文章:

  1. 听我在 OSCON 上关于 Go 性能的演讲
  2. 为什么 Goroutine 的栈是无限大的?
  3. Go 的运行时环境变量的旋风之旅
  4. 没有事件循环的性能

作者简介:

David 是来自澳大利亚悉尼的程序员和作者。

自 2011 年 2 月起成为 Go 的 contributor,自 2012 年 4 月起成为 committer。

联系信息


via: https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast

作者:Dave Cheney 译者:houbaron 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Linux 上的开发工具如此之多,以至于会担心找不到恰好适合你的。

Linux 已经成为工作、娱乐和个人生活等多个领域的支柱,人们已经越来越离不开它。在 Linux 的帮助下,技术的变革速度超出了人们的想象,Linux 开发的速度也以指数规模增长。因此,越来越多的开发者也不断地加入开源和学习 Linux 开发地潮流当中。在这个过程之中,合适的工具是必不可少的,可喜的是,随着 Linux 的发展,大量适用于 Linux 的开发工具也不断成熟。甚至可以说,这样的工具已经多得有点惊人。

为了选择更合适自己的开发工具,缩小选择范围是很必要的。但是这篇文章并不会要求你必须使用某个工具,而只是缩小到五个工具类别,然后对每个类别提供一个例子。然而,对于大多数类别,都会有不止一种选择。下面我们来看一下。

容器

放眼于现实,现在已经是容器的时代了。容器既及其容易部署,又可以方便地构建开发环境。如果你针对的是特定的平台的开发,将开发流程所需要的各种工具都创建到容器映像中是一种很好的方法,只要使用这一个容器映像,就能够快速启动大量运行所需服务的实例。

一个使用容器的最佳范例是使用 Docker,使用容器(或 Docker)有这些好处:

  • 开发环境保持一致
  • 部署后即可运行
  • 易于跨平台部署
  • Docker 映像适用于多种开发环境和语言
  • 部署单个容器或容器集群都并不繁琐

通过 Docker Hub,几乎可以找到适用于任何平台、任何开发环境、任何服务器、任何服务的映像,几乎可以满足任何一种需求。使用 Docker Hub 中的映像,就相当于免除了搭建开发环境的步骤,可以直接开始开发应用程序、服务器、API 或服务。

Docker 在所有 Linux 平台上都很容易安装,例如可以通过终端输入以下命令在 Ubuntu 上安装 Docker:

sudo apt-get install docker.io

Docker 安装完毕后,就可以从 Docker 仓库中拉取映像,然后开始开发和部署了(如下图)。

 title=

图 1: Docker 镜像准备部署

版本控制工具

如果你正在开发一个大型项目,又或者参与团队开发,版本控制工具是必不可少的,它可以用于记录代码变更、提交代码以及合并代码。如果没有这样的工具,项目几乎无法妥善管理。在 Linux 系统上,GitGitHub 的易用性和流行程度是其它版本控制工具无法比拟的。如果你对 Git 和 GitHub 还不太熟悉,可以简单理解为 Git 是在本地计算机上安装的版本控制系统,而 GitHub 则是用于上传和管理项目的远程存储库。 Git 可以安装在大多数的 Linux 发行版上。例如在基于 Debian 的系统上,只需要通过以下这一条简单的命令就可以安装:

sudo apt-get install git

安装完毕后,就可以使用 Git 来实施版本控制了(如下图)。

 title=

图 2:Git 已经安装,可以用于很多重要任务

Github 会要求用户创建一个帐户。用户可以免费使用 GitHub 来管理非商用项目,当然也可以使用 GitHub 的付费模式(更多相关信息,可以参阅价格矩阵)。

文本编辑器

如果没有文本编辑器,在 Linux 上开发将会变得异常艰难。当然,文本编辑器之间孰优孰劣,具体还是要取决于开发者的需求。对于文本编辑器,有人可能会使用 vim、emacs 或 nano,也有人会使用带有 GUI 的编辑器。但由于重点在于开发,我们需要的是一种能够满足开发人员需求的工具。不过我首先要说,vim 对于开发人员来说确实是一个利器,但前提是要对 vim 非常熟悉,在这种前提下,vim 能够满足你的所有需求,甚至还能给你更好的体验。然而,对于一些开发者(尤其是刚开始接触 Linux 的新手)来说,这不仅难以帮助他们快速达成需求,甚至还会是一个需要逾越的障碍。考虑到这篇文章的目标是帮助 Linux 的新手(而不仅仅是为各种编辑器的死忠粉宣传他们拥护的编辑器),我更倾向于使用 GUI 编辑器。

就文本编辑器而论,选择 Bluefish 一般不会有错。 Bluefish 可以从大部分软件库中安装,它支持项目管理、远程文件多线程操作、搜索和替换、递归打开文件、侧边栏、集成 make/lint/weblint/xmllint、无限制撤销/重做、在线拼写检查、自动恢复、全屏编辑、语法高亮(如下图)、多种语言等等。

 title=

图 3:运行在 Ubuntu 18.04 上的 Bluefish

IDE

集成开发环境 Integrated Development Environment (IDE)是包含一整套全面的工具、可以实现一站式功能的开发环境。 开发者除了可以使用 IDE 编写代码,还可以编写文档和构建软件。在 Linux 上也有很多适用的 IDE,其中 Geany 就包含在标准软件库中,它对用户非常友好,功能也相当强大。 Geany 具有语法高亮、代码折叠、自动完成,构建代码片段、自动关闭 XML 和 HTML 标签、调用提示、支持多种文件类型、符号列表、代码导航、构建编译,简单的项目管理和内置的插件系统等强大功能。

Geany 也能在系统上轻松安装,例如执行以下命令在基于 Debian 的 Linux 发行版上安装 Geany:

sudo apt-get install geany

安装完毕后,就可以快速上手这个易用且强大的 IDE 了(如下图)。

 title=

图 4:Geany 可以作为你的 IDE

文本比较工具

有时候会需要比较两个文件的内容来找到它们之间的不同之处,它们可能是同一文件的两个不同副本(有一个经过编译,而另一个没有)。这种情况下,你肯定不想要凭借肉眼来找出差异,而是想要使用像 Meld 这样的工具。 Meld 是针对开发者的文本比较和合并工具,可以使用 Meld 来发现两个文件之间的差异。虽然你可以使用命令行中的文本比较工具,但就效率而论,Meld 无疑更为优秀。

Meld 可以打开两个文件进行比较,并突出显示文件之间的差异之处。 Meld 还允许用户从两个文件的其中一方合并差异(下图显示了 Meld 同时打开两个文件)。

 title=

图 5: 以简单差异的模式比较两个文件

Meld 也可以通过大多数标准的软件库安装,在基于 Debian 的系统上,执行以下命令就可以安装:

sudo apt-get install meld

高效地工作

以上提到的五个工具除了帮助你完成工作,而且有助于提高效率。尽管适用于 Linux 开发者的工具有很多,但对于以上几个类别,你最好分别使用一个对应的工具。


via: https://www.linux.com/learn/intro-to-linux/2018/8/5-essential-tools-linux-development

作者:Jack Wallen 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

React 组件可以通过多种方式决定渲染内容。你可以使用传统的 if 语句或 switch 语句。在本文中,我们将探讨一些替代方案。但要注意,如果你不小心,有些方案会带来自己的陷阱。

三元表达式 vs if/else

假设我们有一个组件被传进来一个 name 属性。 如果这个字符串非空,我们会显示一个问候语。否则,我们会告诉用户他们需要登录。

这是一个只实现了如上功能的无状态函数式组件(SFC)。

const MyComponent = ({ name }) => {
  if (name) {
    return (
      <div className="hello">
        Hello {name}
      </div>
    );
  }
  return (
    <div className="hello">
      Please sign in
    </div>
  );
};

这个很简单但是我们可以做得更好。这是使用 三元运算符 conditional ternary operator 编写的相同组件。

const MyComponent = ({ name }) => (
  <div className="hello">
    {name ? `Hello ${name}` : 'Please sign in'}
  </div>
);

请注意这段代码与上面的例子相比是多么简洁。

有几点需要注意。因为我们使用了箭头函数的单语句形式,所以隐含了return 语句。另外,使用三元运算符允许我们省略掉重复的 <div className="hello"> 标记。

三元表达式 vs &&

正如您所看到的,三元表达式用于表达 if/else 条件式非常好。但是对于简单的 if 条件式怎么样呢?

让我们看另一个例子。如果 isPro(一个布尔值)为真,我们将显示一个奖杯表情符号。我们也要渲染星星的数量(如果不是 0)。我们可以这样写。

const MyComponent = ({ name, isPro, stars}) => (
  <div className="hello">
    <div>
      Hello {name}
      {isPro ? '♨' : null}
    </div>
    {stars ? (
      <div>
        Stars:{'☆'.repeat(stars)}
      </div>
    ) : null}
  </div>
);

请注意 else 条件返回 null 。 这是因为三元表达式要有“否则”条件。

对于简单的 if 条件式,我们可以使用更合适的东西:&& 运算符。这是使用 && 编写的相同代码。

const MyComponent = ({ name, isPro, stars}) => (
  <div className="hello">
    <div>
      Hello {name}
      {isPro && '♨'}
    </div>
    {stars && (
      <div>
        Stars:{'☆'.repeat(stars)}
      </div>
    )}
  </div>
);

没有太多区别,但是注意我们消除了每个三元表达式最后面的 : nullelse 条件式)。一切都应该像以前一样渲染。

嘿!约翰得到了什么?当什么都不应该渲染时,只有一个 0。这就是我上面提到的陷阱。这里有解释为什么:

根据 MDN,一个逻辑运算符“和”(也就是 &&):

expr1 && expr2

如果 expr1 可以被转换成 false ,返回 expr1;否则返回 expr2。 如此,当与布尔值一起使用时,如果两个操作数都是 true&& 返回 true ;否则,返回 false

好的,在你开始拔头发之前,让我为你解释它。

在我们这个例子里, expr1 是变量 stars,它的值是 0,因为 0 是假值,0 会被返回和渲染。看,这还不算太坏。

我会简单地这么写。

如果 expr1 是假值,返回 expr1 ,否则返回 expr2

所以,当对非布尔值使用 && 时,我们必须让这个假值返回 React 无法渲染的东西,比如说,false 这个值。

我们可以通过几种方式实现这一目标。让我们试试吧。

{!!stars && (
  <div>
    {'☆'.repeat(stars)}
  </div>
)}

注意 stars 前的双感叹操作符(!!)(呃,其实没有双感叹操作符。我们只是用了感叹操作符两次)。

第一个感叹操作符会强迫 stars 的值变成布尔值并且进行一次“非”操作。如果 stars0 ,那么 !stars 会是 true

然后我们执行第二个操作,所以如果 stars0!!stars 会是 false。正好是我们想要的。

如果你不喜欢 !!,那么你也可以强制转换出一个布尔数比如这样(这种方式我觉得有点冗长)。

{Boolean(stars) && (

或者只是用比较符产生一个布尔值(有些人会说这样甚至更加语义化)。

{stars > 0 && (

关于字符串

空字符串与数字有一样的毛病。但是因为渲染后的空字符串是不可见的,所以这不是那种你很可能会去处理的难题,甚至可能不会注意到它。然而,如果你是完美主义者并且不希望 DOM 上有空字符串,你应采取我们上面对数字采取的预防措施。

其它解决方案

一种可能的将来可扩展到其他变量的解决方案,是创建一个单独的 shouldRenderStars 变量。然后你用 && 处理布尔值。

const shouldRenderStars = stars > 0;
return (
  <div>
    {shouldRenderStars && (
      <div>
        {'☆'.repeat(stars)}
      </div>
    )}
  </div>
);

之后,在将来,如果业务规则要求你还需要已登录,拥有一条狗以及喝淡啤酒,你可以改变 shouldRenderStars 的得出方式,而返回的内容保持不变。你还可以把这个逻辑放在其它可测试的地方,并且保持渲染明晰。

const shouldRenderStars = 
  stars > 0 && loggedIn && pet === 'dog' && beerPref === 'light`;
return (
  <div>
    {shouldRenderStars && (
      <div>
        {'☆'.repeat(stars)}
      </div>
    )}
  </div>
);

结论

我认为你应该充分利用这种语言。对于 JavaScript,这意味着为 if/else 条件式使用三元表达式,以及为 if 条件式使用 && 操作符。

我们可以回到每处都使用三元运算符的舒适区,但你现在消化了这些知识和力量,可以继续前进 && 取得成功了。


作者简介:

美国运通工程博客的执行编辑 http://aexp.io 以及 @AmericanExpress 的工程总监。MyViews !== ThoseOfMyEmployer.


via: https://medium.freecodecamp.org/conditional-rendering-in-react-using-ternaries-and-logical-and-7807f53b6935

作者:Donavon West 译者:GraveAccent 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出