分类 软件开发 下的文章

这些有用的小技巧将改变你在当前最流行的版本控制系统下的工作方式。

 title=

Git 是当前最流行最普遍的版本控制系统之一,它被应用于私有系统和公开网站上各种各样的开发工作。不论我变得对 Git 有多熟悉,似乎总有些功能等待着被发掘。下面分享下和 Git 相关的改变我工作方式的一些小技巧。

1、Git 中的自动纠错

我们每个人都不时在输入时犯拼写错误,但是如果你使能了 Git 的自动纠错功能,你就能让 Git 自动纠正一些输入错误的子命令。

假如你想用命令 git status 来检查状态,但是你恰巧错误地输入了 git stats。通常情况下,Git 会告诉你 ‘stats’ 不是个有效的命令:

$ git stats
git: ‘stats’ is not a git command. See ‘git --help’.

The most similar command is
status

为了避免类似情形,只需要在你的 Git 配置中使能自动纠错功能。

$ git config --global help.autocorrect 1

如果你只想对当前的仓库生效,就省略掉选项 --global

这个命令会使能自动纠错功能。在相应的 Git 官方文档 中可以看到这个命令的详细说明,但是试着敲一下上面的错误命令会使你对这个设置干了什么有个直观的了解:

$ git stats
git: ‘stats’ is not a git command. See ‘git --help’.
On branch master
Your branch is up to date with ‘origin/master’.

nothing to commit, working tree clean

在上面的例子中,Git 直接运行了它建议命令的第一个,也就是 git status,而不是给你展示它所建议的子命令。

2、对提交进行计数

需要对提交进行计数的原因有很多。例如,一些开发人员利用提交计数来判断什么时候递增工程构建序号,也有一些开发人员用提交计数来对项目进展取得一个整体上的感观。

对提交进行计数相当简单而且直接,下面就是相应的 Git 命令:

$ git rev-list --count branch-name

在上述命令中,参数 branch-name 必须是一个你当前仓库里的有效分支名。

$ git rev-list –count master
32
$ git rev-list –count dev
34

3、仓库优化

你的代码仓库不仅对你来说很宝贵,对你所在的组织也一样。通过少数几个惯例你就能使自己的仓库整洁并且保持最新。使用 .gitignore 文件 就是这些最好的惯例之一。通过使用这个文件你可以告诉 Git 不要保存一些不需要记录的文件,如二进制文件、临时文件等等。

当然,你还可以使用 Git 的垃圾回收来进一步优化你的仓库。

$ git gc --prune=now --aggressive

这个命令在你和你的团队经常使用 pull 或者 push 操作的时候很有帮助。

它是一个内部工具,能清理掉你的仓库里没法访问或者说“空悬”的 Git 对象。

4、给未追踪的文件来个备份

大多数时候,删除所有未追踪的文件是安全的。但很多时候也有这么一种场景,你想删掉这些未追踪的文件同时也想做个备份防止以后需要用到。

Git 组合一些 Bash 命令和管道操作,可以让你可以很容易地给那些未追踪的文件创建 zip 压缩包。

$ git ls-files --others --exclude-standard -z |\
  xargs -0 tar rvf ~/backup-untracked.zip

上面的命令就生成了一个名字为 backup-untracked.zip 的压缩包文件(当然,在 .gitignore 里面忽略了的文件不会包含在内)。

5、了解你的 .git 文件夹

每个仓库都有一个 .git 文件夹,它是一个特殊的隐藏文件夹。

$ ls -a
. … .git

Git 主要通过两个东西来工作:

  1. 当前工作树(你当前检出的文件状态)
  2. 你的 Git 仓库的文件夹(准确地说,包含版本信息的 .git 文件夹的位置)

这个文件夹存储了所有参考信息和一些其他的如配置、仓库数据、HEAD 状态、日志等更多诸如此类的重要细节。

一旦你删除了这个文件夹,尽管你的源码没被删,但是类似你的工程历史记录等远程信息就没有了。删除这个文件夹意味着你的工程(至少本地的复制)不再在版本控制的范畴之内了。这也就意味着你没法追踪你的修改;你没法从远程仓拉取或推送到远程仓了。

通常而言,你需要或者应当对你的 .git 文件夹的操作并不多。它是被 Git 管理的,而且大多数时候是一个禁区。然而,在这个文件夹内还是有一些有趣的工件,比如说当前的 HEAD 状态在内的就在其中。

$ cat .git/HEAD
ref: refs/heads/master

它也隐含着对你仓库地描述:

$ cat .git/description

这是一个未命名的仓库;通过编辑文件 ‘description’ 可以给这个仓库命名。

Git 钩子文件夹连同一些钩子文件例子也在这里。参考这些例子你就能知道 Git 钩子能干什么了。当然,你也可以 参考这个 Seth Kenlon 写的 Git 钩子介绍

6、浏览另一个分支的文件

有时,你会想要浏览另一个分支下某个文件的内容。这其实用一个简单的 Git 命令就可以实现,甚至都不用切换分支。

设想你有一个命名为 README.md 的文件,并且它在 main 分支上。当前你正工作在一个名为 dev 的分支。

用下面的 Git 命令,在终端上就行。

$ git show main:README.md

一旦你执行这个命令,你就能在你的终端上看到 main 分支上该文件的内容。

7、Git 中的搜索

用一个简单的命令你就能在 Git 中像专业人士一样搜索了。更有甚者,尽管你不确定你的修改在哪次提交或者哪个分支上,你依然能搜索。

$ git rev-list --all | xargs git grep -F ''

例如,假设你想在你的仓库中搜索字符串 “font-size: 52 px;"

$ git rev-list –all | xargs git grep -F ‘font-size: 52 px;’
F3022…9e12:HtmlTemplate/style.css: font-size: 52 px;
E9211…8244:RR.Web/Content/style/style.css: font-size: 52 px;

试试这些小技巧

我希望这些小技巧对你是有用的,或者增加你的生产力或者节省你的大量时间。

你也有一些喜欢的 Git 技巧 吗?在评论区分享吧。


via: https://opensource.com/article/20/10/advanced-git-tips

作者:Rajeev Bera 选题:lujun9972 译者:BoosterY 校对:wxy

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

Cube.js 是一个开源的分析平台,可以作为数据源和应用之间的中间层。

 title=

数据分析是一个时髦的领域,有许多解决方案可供选择。其中之一是 Cube.js,这是一个开源的分析平台。你可以把 Cube.js 看作是你的数据源和应用之间的一个中间层。

如下图所示,Cube.js 支持无服务器数据仓库和大多数现代关系型数据库管理系统 (RDBMS)。你可以使用任何用于数据可视化的 JavaScript 前端库,而 Cube.js 将负责其他工作,包括访问控制、性能、并发性等。

 title=

主要优点

当我向我们的社区成员询问 Cube.js 的主要优点时,他们经常提到:

  • 它的抽象层:配置 Cube.js 后,人们说他们不再需要担心性能优化、资源管理、SQL 专业知识等问题。许多人把 Cube.js 称为 “黑盒”,因为它的抽象层帮助他们专注于理解数据,而不是实施细节。
  • 易于定制:由于 Cube.js 是可视化的,它很容易与前端框架集成,建立看起来像用户自己平台的解决方案。大多数商业平台(如 Looker、Tableau 等)需要更多的定制工作来与他们的基础设施整合。许多用户说,定制的便利性与抽象层相结合,使他们能够减少数据分析平台的开发时间。
  • 社区支持:在开始使用 Cube.js 时,人们通常会从社区成员那里得到帮助(特别是在我们的 Slack),许多人提到社区支持是一个关键的入门资源。

访问 用户故事页面,阅读更多关于人们使用 Cube.js 的经验以及他们如何使用它。

开始使用

如果你想了解 Cube.js:

  • 进入我们的 文档页面,点击开始,并按照指示在你的笔记本电脑或工作站上启动和运行 Cube.js。
  • 当你进入 Developer Playground,你将能够生成数据模式,执行查询,并建立仪表盘,以看到 Cube.js 的运行。

在你启动和运行 Cube.js 之后,这里有一些有用的资源:

  • 文档:我们把大量的精力放在我们的文档上,因为它是开源社区的重要资源。我们还在我们的文档页面和 YouTube 频道的 入门播放列表 中添加了视频剪辑。
  • Discourse:Cube.js 论坛是最近增加的,社区成员可以在这里分享他们的使用案例、技巧和窍门等,这样我们就可以建立一个社区知识库。
  • GitHub: 你可以在这里找到 Cube.js 的代码,社区成员可以通过 问题页面 提交错误或功能请求。我们还在 GitHub 上发布了我们的 季度路线图,以便每个人都能看到我们正在进行的工作。
  • 每月社区电话会议:我们在每个月的第二个星期三举行电话会议,讨论社区更新,展示功能演示,并邀请社区成员分享他们的使用案例。你可以在 社区电话会议页面 上找到电话会议的日程,你也可以在我们 YouTube 频道的 社区电话会议播放列表 上找到过去的电话会议录音。

就像任何好的开源项目一样,Cube.js 有许多软件贡献者。如果你想查看社区的拉取请求(PR),请搜索带有 pr:community 标签的 PR。如果你想寻找你可以回答的问题,请搜索带有 good first issue 或者 help wanted 标签的问题。

我希望你试试 Cube.js。如果你有任何问题,请随时在下面留言或在 Cube.js Slack 上找我!


via: https://opensource.com/article/21/6/cubejs

作者:Ray Paik 选题:lujun9972 译者:geekpi 校对:wxy

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

使用微控制器、传感器、Python 以及 MQTT 持续追踪温室的温度、湿度以及环境光。

 title=

CircuitPython 提供了一种和微控制器板进行交互的革命性方式。这篇文章介绍了如何使用 CircuitPython 来监测温室的温度、湿度以及环境光,并且使用 CircuitPython MQTT 客户端将结果发布到一个 MQTT 中介 broker 。你可以在若干个程序中订阅 MQTT 队列并进一步处理信息。

这个项目使用一个简单的 Python 程序来运行 Web 服务器,它发布一个 Prometheus 格式的采集端点,拉取监控指标到 Prometheus 进行不间断的监控。

关于 CircuitPython

CircuitPython 是一个由 Adafruit 创建的开源 Python 发行版,用于运行在低成本微控制器开发板上。CircuitPython 为与 兼容的开发板 的交互提供了简单的开发体验。你可以在连接你的开发板时挂载的 CIRCUITPYTHON 根驱动器上创建一个 code.py 文件来启动你的程序。CircuitPython 还为开发板提供了一个串行连接,包含一个交互式解释器(REPL)会话,你可以使用 Python 代码实时和开发板进行交互。

Adafruit 的网站提供了大量的文档,可以帮助你开始使用 CircuitPython。首先,参考下《欢迎来到 CircuitPython》指南。这份指南能够帮助你开始使用 CircuitPython 在开发板上运行代码以及和 REPL 交互。它还记录了如何安装 Adafruit 的 CircuitPython 库合集和范例,可以用在它出售的许多开发板和传感器上。接下来,阅读《CircuitPython 基础》指南来学习更多关于其功能的信息,里面还有链接指向在特定及兼容的开发板上使用 CircuitPython 的相关信息。最后,就如所有开源软件一样,你可以深入 CircuitPython 的源码,发布议题,以及做出贡献。

微控制器设置

微控制器系统非常简单。要完成这个示例项目,你会需要:

  • 树莓派 4:你需要一台电脑来给微控制器系统编程,我用的是树莓派 4。
  • CircuitPython 兼容的微控制器:我用的是 Adafruit Feather S2,带有内置 WiFi,环境光传感器,Qwiic 线缆输入。
  • 微控制器 WiFi:Feather S2 内置了 WiFi。如果你的微控制器没有,你需要给开发板找个 WiFi 扩展板。
  • 传感器:Feather S2 有个内置的环境光传感器,所以我还需要一个温湿度传感器。有很多不同厂商的产品可以选择,包括 Adafruit、SparkFun、亚马逊。我用的是一个 Adafruit 传感器,带有 Feather S2 输入兼容的 Qwiic 线缆。尽管多数 SparkFun 传感器可以在 Adafruit 库下工作,但如果你不是从 Adafruit 购买的传感器,你可能还是需要自己去找到它兼容 CircuitPython 的 Python 库。
  • 跳线和线缆:为了避免使用面包板或焊接,我使用 Adafruit Qwiic 线缆。SparkFun 销售的包含不同长度的线缆套装中也有它。

在将微控制器连接到你的电脑之前,将传感器连接到微控制器上。

 title=

现在你可以将微控制器用 USB 数据线连接到你的电脑。

MQTT 中介

你可以使用 这份说明 来在树莓派的系统上安装 Mosquitto MQTT 中介 和 Mosquitto 客户端。如果你想把树莓派做为长期服务器使用,在你的网络上给树莓派 4 设置一个静态 IP 地址。Mosquitto 中介运行起来之后,创建一份 用户名/密码文件,设置客户端向中介发布和订阅消息时用的认证信息。

你可以用树莓派上的 Mosquitto 客户端来测试 MQTT 中介。打开两个终端(如果你是无界面运行的话打开两个 SSH 会话):

在终端一输入:

mosquitto_sub -h localhost -u $user -P $pass -t "mqtt/test"

这条命令会启动一个持续运行的进程,监听发布到 mqtt/test 队列的消息。

在终端二输入:

mosquitto_pub -h localhost -u $user -P $pass -t "mqtt/test" -m hello`

这条命令会向 mqtt/test 队列发布一条消息,它应该会显示在终端一的输出里。

现在你可以中止终端一运行的 sub 命令了。

Mosquitto 中介允许客户端发布消息到任何队列,甚至没有任何订阅的队列也可以。这些消息会永久丢失,但这不会阻止客户端继续发布消息。

打开第三个终端,订阅下列队列(你的控制器会发布消息到这些队列上):

  • greenhouse/temperature
  • greenhouse/light
  • greenhouse/humidity

给微控制器编码

现在你已经准备好给微控制器编码,发布它的监测指标到树莓派 4 上运行的 MQTT 中介上了。

Adafruit 有 出色的文档,指导你使用 CircuitPython 库合集 的库来将你的微控制器连接到 WiFi 路由器,并发布监测指标到 MQTT 中介上。

安装下列库到 CIRCUITPYTHON/lib 目录,温室监控会用到它们。这些库在 Adafruit 的 CircuitPython 库合集中都有提供:

  • adafruit_bus_device:一个带有多个 .mpy 文件的 Python 包文件夹(.mpy 是经过压缩的 Python 文件,用以节省空间)
  • adafruit_requests:单个 .mpy 文件
  • adafruit_register:一个包文件夹
  • adafruit_minimqtt:一个包文件夹
  • adafruit_si7021:单个 .mpy 文件,用来支持温湿度传感器

库装好了之后,将以下代码写入 CIRCUITPYTHON 文件夹的 code.py 文件中:

import time
import ssl
import socketpool
import wifi
import adafruit_minimqtt.adafruit_minimqtt as MQTT
import board
from digitalio import DigitalInOut, Direction, Pull
from analogio import AnalogIn
import adafruit_si7021
 
# Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and
# "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other
# source control.
# pylint: disable=no-name-in-module,wrong-import-order
try:
        from secrets import secrets
except ImportError:
        print("WiFi secrets are kept in secrets.py, please add them there!")
        raise
 
print("Connecting to %s" % secrets["ssid"])
wifi.radio.connect(secrets["ssid"], secrets["password"])
print("Connected to %s!" % secrets["ssid"])
### Feeds ###
light_feed = "greenhouse/light"
temp_feed = "greenhouse/temperature"
humidity_feed = "greenhouse/humidity"
 
# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
        # This function will be called when the client is connected
        # successfully to the broker.
        print("Connected to MQTT!")
 
def disconnected(client, userdata, rc):
        # This method is called when the client is disconnected
        print("Disconnected from MQTT!")
 
 
def get_voltage(pin):
        return (pin.value * 3.3) / 65536
 
# Create a socket pool
pool = socketpool.SocketPool(wifi.radio)
 
# Set up a MiniMQTT Client
mqtt_client = MQTT.MQTT(
        broker=secrets["broker"],
        port=secrets["port"],
        username=secrets["aio_username"],
        password=secrets["aio_key"],
        socket_pool=pool,
        ssl_context=ssl.create_default_context(),
)
 
# Setup the callback methods above
mqtt_client.on_connect = connected
mqtt_client.on_disconnect = disconnected
 
# Connect the client to the MQTT broker.
print("Connecting to MQTT...")
mqtt_client.connect()
 
# Create library object using our Bus I2C port
sensor = adafruit_si7021.SI7021(board.I2C())
light_pin = AnalogIn(board.IO4)
 
while True:
        # Poll the message queue
        mqtt_client.loop()
 
        # get the current temperature
        light_val = get_voltage(light_pin)
        temp_val = ((sensor.temperature * 9)/5) + 32
        humidity_val = sensor.relative_humidity
 
        # Send a new messages
        mqtt_client.publish(light_feed, light_val)
        mqtt_client.publish(temp_feed, temp_val)
        mqtt_client.publish(humidity_feed, humidity_val)
        time.sleep(0.5)

保存你的代码。然后连接到串行监视器,看程序连接到你的 MQTT 中介。你还可以将树莓派 4 上的终端切换到订阅了它的发布队列的终端来查看输出。

处理监测指标

像 MQTT 这样的发布/订阅工作流给微控制器系统提供了诸多好处。你可以有多个微控制器 + 传感器来回报同一个系统的不同指标或并行回报相同指标的若干读数。你还可以有多个不同进程订阅各个队列,并行地对这些消息进行回应。甚至还可以有多个进程订阅相同的队列,对消息做出不同的动作,比如数值过高时发送通知邮件或将消息发送到另一个 MQTT 队列上去。

另一个选项是让一个微控制器订阅一个外部队列,可以发送信号告诉微控制器做出动作,比如关闭或开始一个新会话。最后,发布/订阅工作流对低功耗微控制器系统更佳(比如那些使用电池或太阳能的系统),因为这些设备可以在更长的延迟周期后批量发布监测指标,并在回报的间隔期间关闭大量消耗电量的 WiFi 广播。

要处理这些监测指标,我创建了一个 Python 客户端,使用 Paho Python MQTT 客户端 订阅监测指标队列。我还使用官方的 Prometheus Python 客户端 创建了一个 Web 服务器,它产生一个符合 Prometheus 标准的采集端点,使用这些监测指标作为面板信息。Prometheus 服务器和 Mosquitto MQTT 中介我都是运行在同一个树莓派 4 上的。

from prometheus_client import start_http_server, Gauge
import random
import time
import paho.mqtt.client as mqtt

gauge = {
  "greenhouse/light": Gauge('light','light in lumens'),
  "greenhouse/temperature": Gauge('temperature', 'temperature in fahrenheit'),
  "greenhouse/humidity": Gauge('humidity','relative % humidity')
}

try:
        from mqtt_secrets import mqtt_secrets
except ImportError:
        print("WiFi secrets are kept in secrets.py, please add them there!")
        raise

def on_connect(client, userdata, flags, rc):
        print("Connected with result code "+str(rc))
        # Subscribing in on_connect() means that if we lose the connection and
        # reconnect then subscriptions will be renewed.
        client.subscribe("greenhouse/light")
        client.subscribe('greenhouse/temperature')
        client.subscribe('greenhouse/humidity')

def on_message(client, userdata, msg):
        topic = msg.topic
        payload = msg.payload
        gauge[topic].set(payload)

client = mqtt.Client()
client.username_pw_set(mqtt_secrets["mqtt_user"],mqtt_secrets['mqtt_password'])
client.on_connect = on_connect
client.on_message = on_message
client.connect('localhost',1883,60)

if __name__ == '__main__':
        # Start up the server to expose the metrics.

        client = mqtt.Client()
        client.username_pw_set('london','abc123')
        client.on_connect = on_connect
        client.on_message = on_message
        client.connect('localhost',1883,60)

        start_http_server(8000)
        client.loop_forever()

然后我配置 Prometheus 服务器采集端点数据到 localhost:8000

你可以在 Github 上访问 温室 MQTT 微控制器 这个项目的代码,项目采用 MIT 许可证授权。


via: https://opensource.com/article/21/5/monitor-greenhouse-open-source

作者:Darin London 选题:lujun9972 译者:alim0x 校对:wxy

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

一旦你理解了一般原则,C++ 类成员函数指针不再那么令人生畏。

 title=

如果你正在寻找性能、复杂性或许多可能的解决方法来解决问题,那么在涉及到极端的情况下,C++ 总是一个很好的选择。当然,功能通常伴随着复杂性,但是一些 C++ 的特性几乎难以分辨。根据我的观点,C++ 的 类成员函数指针 也许是我接触过的最复杂的表达式,但是我会先从一些较简单的开始。

文章中的例子可以在我的 Github 仓库 里找到。

C 语言:函数指针

让我们先从一些基础开始:假设你有一个函数接收两个整数作为参数返回一个整数:

int sum(int a, int b) {
    return a+b;
}

在纯 C 语言中,你可以创建一个指向这个函数的指针,将其分配给你的 sum(...) 函数,通过解引用来调用它。函数的签名(参数、返回类型)必须符合指针的签名。除此之外,一个函数指针表现和普通的指针相同:

int (*funcPtrOne)(int, int);

funcPtrOne = ∑

int resultOne = funcPtrOne(2, 5);

如果你使用指针作为参数并返回一个指针,这会显得很丑陋:

int *next(int *arrayOfInt){
    return ++arrayOfInt;
}

int *(*funcPtrTwo)(int *intPtr);

funcPtrTwo = &next;

int resultTwo = *funcPtrTwo(&array[0]);

C 语言中的函数指针存储着子程序的地址。

指向类成员函数的指针

让我们来进入 C++:好消息是你也许不需要使用类成员函数指针,除非在一个特别罕见的情况下,比如说接下来的例子。首先,你已经知道定义一个类和其中一个成员函数:

class MyClass
{
public:

    int sum(int a, int b) {
        return a+b;
    }

};

1、定义一个指针指向某一个类中一个成员函数

声明一个指针指向 MyClass 类成员函数。在此时,你并不知道想调用的具体函数。你仅仅声明了一个指向 MyClass 类中任意成员函数的指针。当然,签名(参数、返回值类型)需要匹配你接下想要调用的 sum(...) 函数:

int (MyClass::*methodPtrOne)(int, int);

2、赋值给一个具体的函数

为了和 C 语言(或者 静态成员函数#Static_method))对比,类成员函数指针不需要指向绝对地址。在 C++ 中,每一个类中都有一个虚拟函数表(vtable)用来储存每个成员函数的地址偏移量。一个类成员函数指针指向 vtable 中的某个条目,因此它也只存储偏移值。这样的原则使得 多态 变得可行。

因为 sum(...) 函数的签名和你的指针声明匹配,你可以赋值签名给它:

methodPtrOne = &MyClass::sum;

3、调用成员函数

如果你想使用指针调用一个类成员函,你必须提供一个类的实例:

MyClass clsInstance;
int result = (clsInstance.*methodPtrOne)(2,3);

你可以使用 . 操作符来访问,使用 * 对指针解引用,通过提供两个整数作为调用函数时的参数。这是丑陋的,对吧?但是你可以进一步应用。

在类内使用类成员函数指针

假设你正在创建一个带有后端和前端的 客户端/服务器 原理架构的应用程序。你现在并不需要关心后端,相反的,你将基于 C++ 类的前端。前端依赖于后端提供的数据完成初始化,所以你需要一个额外的初始化机制。同时,你希望通用地实现此机制,以便将来可以使用其他初始化函数(可能是动态的)来拓展你的前端。

首先定义一个数据类型用来存储初始化函数(init)的指针,同时描述何时应调用此函数的信息(ticks):

template<typename T>
struct DynamicInitCommand {
    void (T::*init)();     // 指向额外的初始化函数
    unsigned int ticks;    // 在 init() 调用后 ticks 的数量
};

下面一个 Frontend 类示例代码:

class  Frontend
{
public:

    Frontend(){
        DynamicInitCommand<Frontend> init1, init2, init3;

        init1 = { &Frontend::dynamicInit1, 5};
        init2 = { &Frontend::dynamicInit2, 10};
        init3 = { &Frontend::dynamicInit3, 15};

        m_dynamicInit.push_back(init1);
        m_dynamicInit.push_back(init2);
        m_dynamicInit.push_back(init3);
    }
   
    void  tick(){
        std::cout << "tick: " << ++m_ticks << std::endl;
       
        /* 检查延迟初始化 */
        std::vector<DynamicInitCommand<Frontend>>::iterator  it = m_dynamicInit.begin();

        while (it != m_dynamicInit.end()){
            if (it->ticks < m_ticks){
                 
                if(it->init)
                    ((*this).*(it->init))(); // 这里是具体调用

                it = m_dynamicInit.erase(it);

            } else {
                it++;
            }
        }
    }
   
    unsigned  int  m_ticks{0};
   
private:

    void  dynamicInit1(){
        std::cout << "dynamicInit1 called" << std::endl;
    };

    void  dynamicInit2(){
        std::cout << "dynamicInit2 called" << std::endl;
    }

    void  dynamicInit3(){
        std::cout << "dynamicInit3 called" << std::endl;
    }

    unsigned  int  m_initCnt{0};
    std::vector<DynamicInitCommand<Frontend> > m_dynamicInit;
};

Frontend 完成实例化后,tick() 函数会被后端以固定的时间时间调用。例如,你可以每 200 毫秒调用一次:

int  main(int  argc, char*  argv[]){
    Frontend frontendInstance;

    while(true){
        frontendInstance.tick(); // 仅用于模拟目的
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

Fronted 有三个额外的初始化函数,它们必须根据 m_ticks 的值来选择调用哪个。在 ticks 等于何值调用哪个初始化函数的信息存储在数组 m_dynamicInit 中。在构造函数(Frontend())中,将此信息附加到数组中,以便在 5、10 和 15 个 tick 后调用其他初始化函数。当后端调用 tick() 函数时,m_ticks 值会递增,同时遍历数组 m_dynamicInit 以检查是否必须调用初始化函数。

如果是这种情况,则必须通过引用 this 指针来取消引用成员函数指针:

((*this).*(it->init))()

总结

如果你并不熟悉类成员函数指针,它们可能会显得有些复杂。我做了很多尝试和经历了很多错误,花了一些时间来找到正确的语法。然而,一旦你理解了一般原理后,方法指针就变得不那么可怕了。

这是迄今为止我在 C++ 中发现的最复杂的语法。 你还知道更糟糕的吗? 在评论中发布你的观点!


via: https://opensource.com/article/21/2/ccc-method-pointers

作者:Stephan Avenwedde 选题:lujun9972 译者:萌新阿岩 校对:wxy

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

Java 具有功能强大、多样化、可拓展、有趣的特点。这就是 Java 为什么被我们广泛使用,也是我们如何正确使用它的方式。

 title=

Java 是在 1995 年发布的,当我写这篇文章的时候,它已经 26 岁了。起初它是专有的,但在 2007 年,Java 基于 GPL 协议被开源发布了。如果想要理解是什么使得 Java 变得非常重要,你就必须理解它声称要解决的是什么样的问题,从而你就能理解它让开发者和用户受益的原因和方式。

理解 Java 解决了什么问题的最好方式就是进行软件开发,当然啦,如果不做开发,仅仅只是使用软件也会是一个很好的开始。作为一名开发人员,当你将在自己的本地计算机上运行良好的软件部署到其他计算机上运行时,一些稀奇古怪的麻烦可能就出现了,从而导致软件可能无妨正常运行。软件本应正常工作,但每个程序员都明白,一些问题总是会被忽视。当你在另一个操作系统上尝试运行该软件时,情况就变得更加复杂了。这也是为什么在每一个软件的获取页面上都会有针对不同的操作系统有对应下载按钮的原因:Windows 的、macOS 的、Linux 的、移动端的、甚至许多其他操作系统环境的下载选项。

作为一名用户,一个典型的场景是你想下载一些优秀的软件,但它却不适用于你的平台。遗憾的是这样的情况仍然发生在当下非常先进的计算机上,它们可以在计算机中运行虚拟机,通过仿真使老式视频游戏保持活力,甚至可以放在你的口袋里,但软件交付实际上相当困难。

有没有更好的办法?可能会有吧。

1、一次编码,任意环境都能跑通

令人惊讶甚至是失望的是,代码是特定于操作系统和环境的。代码需要从对人友好的高级程序设计语言编译成机器语言,即被设计可以用于让 CPU 响应的一系列二进制指令。在先进的计算机世界中,我们很难理解为什么不能仅仅只要编写代码,就能将它发送给任何一个想要运行它的平台,无需担忧它们正处在什么样的平台中。

Java 可以解决这种不协调的问题。它的代码是可以跨平台进行工作的,在任何运行它的系统上都执行相同的工作。Java 实现这一壮举的方法起初是有悖常理的。在某种程度上,Java 只与一台计算机兼容。奇怪的是,这台电脑实际上并不存在。Java 代码的目标计算机是Java 虚拟机(JVM)。这是一个由 Java 的创建者编写的程序,可用于你能想到的任何计算机设备。只要你安装了它,你运行的任何 Java 代码都会由你计算机中的这台“虚拟”计算机进行处理。Java 代码会由 JVM 执行,JVM 向你的计算机发送适当的特定于平台的指令,因此所有工作在每个操作系统和架构上都是一样的。

当然,Java 使用的方法并不是这里的真正的卖点。大多数用户和许多开发人员并不关心软件兼容性是如何实现的,只关心它是否具备兼容性。许多语言都承诺提供跨平台的功能,通常情况下,这个承诺最终都是真的,但是这个过程并不总是容易实现的。编程语言必须针对其目标平台进行编译,脚本语言需要特定于平台的解释器,而且两者都很难确保对底层系统资源的一致访问。跨平台支持变得越来越好,库可以帮助转换路径、环境变量和设置,并且一些框架(特别是 Qt)在弥补外设访问的差距方面做了很多工作。但是,Java 始终可靠地提供它的兼容性。

2、明智的代码

Java 的语法即使是在最好的方面也很无聊。如果你把所有流行的编程语言都放在一个摇滚杯中,那么你会得到 Java。通过观察 Java 编写的源代码,你或多或少会均匀地看到所有特定的编程表达方式。括号表示函数和流程控制的范围、变量在使用前被明确地声明和实例化,并且表达式具有清晰一致的结构。

我发现 Java 学习过程中通常会鼓励自学成才的程序员使用结构化程度较少的语言编写更精炼的代码。从网上学习的源代码中收集到的技术中,有许多“基本”编程经验是你无法学到的,比如以 Java 公开字段的风格进行全局变量声明、正确地预测和处理异常、使用类和函数、和许多其他的技术。从 Java 借鉴的一点小改动可以产生很大的不同。

3、脚手架和支持

流行的编程语言都有很好的支持系统,这也是使得其变成流行语言的原因。它们都有很多文档资料,有针对它们的集成开发环境或 IDE 扩展、示例代码、免费和付费培训和开发者社区。在另一方面,当你在尝试做某事遇到困难时,似乎没有任何编程语言有足够的支持。

我不能说 Java 可以摆脱这两个普遍但又相互矛盾的事实。尽管如此,我发现当我需要一个 Java 库时,我必然能为给定的任务找到多个选项。通常我不想使用一个库的原因是我不喜欢它的开发人员如何实现我需要的功能,它的许可证与我喜欢的有所不同,或者有其他琐碎的争议点。当一门语言得到大量支持时,我就会很多的选择性。我可以从许多合适的解决方案中选择一个最能满足我需求的,不论我的需求多么微不足道都能被最好得满足。

更好的是,围绕 Java 有一个健康的基础设施。像 Apache AntGradleMaven 等工具可以帮助管理构建和交付的过程。像 Sonatype Nexus 等服务帮助实现监控的安全性。SpringGrails 使 Web 开发变得更加容易,而 QuarkusEclipse Che 有助于云上的开发。

在接触 Java 语言本身时,你甚至可以选择使用什么样的版本。OpenJDK 提供经典的、官方的 Java,而 Groovy 是一种类似于脚本语言的简化方法(你可以把它比作 Python),而 Quarkus 提供了一个容器优先开发的框架。

还有很多,但现在已经足以说明 Java 是一个完整的生态了,无论你想在其中寻找什么。

此外,简单易学

事实证明,Java 对我和各行各业的许多开发人员来说是一个明智的解决方案。以下是我喜欢使用 Java 的一些原因。

你可能听说过或推断出 Java 是一种“专业”语言,只适用于笨重的政府网站,专供“真正的”开发人员使用。千万不要被 Java 25 年以来的各种名声所迷惑!它的可怕程度只有它名声的一半,这意思是,并不比其他任何语言更可怕。

编程很困难的这件事是无法回避的,它要求你基于逻辑进行思考,学习一种比母语表达方式更少的新语言,要你弄清楚如何解决困难的问题,使它们可以使用你的程序完成自动化的执行,没有语言可以避免这些问题。

然而,编程语言的学习曲线的差异令人惊讶。有些一开始很容易,但当你开始探索细节时就会变得复杂。换句话说,打印“hello world”可能只需要一行代码,但当你学习到了类和函数, 你相当于开始重新学习这门语言(或者至少是它的数据模型)。Java 从一开始就是 Java,一旦你学会了它,就可以使用它的许多技巧和便利。

简而言之: 去学习 Java 吧!它具有功能强大、多样化、可拓展、有趣的特点。为了给你提供帮助, 下载我们的 Java 备忘单, 它包含你在开发前十个项目时需要的所有基本语法。在那之后,你就不再需要它了,因为 Java 具有完美的一致性和可预测性。来享受它吧!


via: https://opensource.com/article/21/5/java

作者:Seth Kenlon 选题:lujun9972 译者:PearFL 校对:wxy

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

创建一个 API(应用程序接口),我们所要做的远远不止是让它能“正常工作”。

 title=

如果你正在构建基于 C/S 模型的应用程序,那么你需要一个应用程序接口(API)。API 就是一种非常清晰而又明确的定义,它是一个 进程 process 与另一个进程之间明确定义的边界。Web 应用中我们常见的边界定义就是 REST/JSON API。

虽然很多开发者可能主要关注在如何让 API 正常工作(或功能正常),但却还有一些“非功能性”的要求也是需要他们注意的。所有的 API 必须具备 的 4 个非功能性的要求是:

  • 安全
  • 文档
  • 验证
  • 测试

安全

在软件开发中,安全是最基本的要求。对于 API 开发者来说,API 的安全性主要包含以下 4 个方面:

  1. HTTPS/SSL 证书
  2. 跨域资源共享
  3. 身份认证与 JSON Web 令牌
  4. 授权与作用域

1、HTTPS/SSL 证书

Web 应用的黄金标准是使用 SSL 证书的 HTTPS 协议。Let's Encrypt 可以帮你达到这一目的。Let's Encrypt 来自于非营利性的互联网安全研究小组(ISRG),它是一个免费的、自动化的、开放的证书颁发机构。

Let's Encrypt 的软件会为你的域(LCTT 译注:包含域名、IP 等信息)生成中央授权证书。而正是这些证书确保了从你的 API 到客户端的数据载荷是点对点加密的。

Let's Encrypt 支持证书管理的多种部署方案。我们可以通过查看 文档 来找出最适合自己需要的方案。

2、跨域资源共享

跨域资源共享 Cross-origin resource sharing (CORS)是一个针对浏览器的安全策略预检。如果你的 API 服务器与发出请求的客户端不在同一个域中,那么你就要处理 CORS。例如,如果你的服务器运行在 api.domain-a.com 并且接到一个来自 domain-b.com 的客户端的请求,那么 CORS 就会(让浏览器)发送一个 HTTP 预检请求,以便查看你的 API 服务是否会接受来自此客户域的客户端请求。

来自 MDN 的定义

“跨域资源共享(CORS)是一种基于 HTTP 头的机制,这种机制允许服务器标记除自身源外的其他任何来源(域、方案或端口)。而对于这些被服务器标识的源,浏览器应该允许我们从它们加载资源。”

 title=

(MDN文档CC-BY-SA 2.5)

另外,有很多用于 Node.js 的辅助库来 帮助 API 开发者处理 CORS

3、身份认证与 JSON Web 令牌

有多种方法可以验证你的 API 中的认证用户,但最好的方法之一是使用 JSON Web 令牌(JWT),而这些令牌使用各种知名的加密库进行签名。

当客户端登录时,身份管理服务会向客户端提供一个 JWT。然后,客户端可以使用这个令牌向 API 发出请求,API 收到请求后,从服务器读取公钥或私密信息来验证这个令牌。

有一些现有的库,可以帮助我们对令牌进行验证,包括 jsonwebtoken。关于 JWT 的更多信息,以及各种语言中对其的支持库,请查看 JWT.io

import jwt from 'jsonwebtoken'

export default function (req, res, next) {
    // req.headers.authorization Bearer token
    const token = extractToken(req)
    jwt.verify(token, SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
        if (err) { next(err) }
        req.session = decoded
        next()
    })
}

4、授权与作用域

认证(或身份验证)很重要,但授权同样很重要。也就是说,经过验证的客户端是否有权限让服务器执行某个请求呢?这就是作用域的价值所在。当身份管理服务器对客户端进行身份认证,且创建 JWT 令牌时,身份管理服务会给当前客户提供一个作用域,这个作用域将会决定当前经过验证的客户的 API 请求能否被服务器执行。这样也就免去了服务器对访问控制列表的一些不必要的查询。

作用域是一个文本块(通常以空格分隔),用于描述一个 API 端点的访问能力。一般来说,作用域被分为资源与动作。这种模式对 REST/JSON API 很有效,因为它们有相似的 RESOURCE:ACTION 结构。(例如,ARTICLE:WRITEARTICLE:READ,其中 ARTICLE 是资源,READWRITE 是动作)。

作用域的划分让我们的 API 能够专注于功能的实现,而不是去考虑各种角色和用户。身份访问管理服务可以将不同的角色和用户分配不同的权限范围,然后再将这些不同的作用域提供给不同的 JWT 验证中的客户。

总结

当我们开发和部署 API 时,安全应该一直是最重要的要求之一。虽然安全性是一个比较宽泛的话题,但如果能解决上面四个方面的问题,这对于你的 API 来说,在生产环境中将会表现得更好。

文档

有什么能比没有文档更糟糕?过期的文档。

开发者对文档真的是又爱又恨。尽管如此,文档仍然是 API 定义是否完善的一个关键部分。开发者需要从文档中知道如何使用这些 API,且你创建的文档对于开发者如何更好地使用 API 也有着非常巨大的作用。

创建 API 文档,我们需要关注下面三个方面:

  1. 开发者入门文档(自述文件/基本介绍)
  2. 技术参考(规范/说明书)
  3. 使用方法(入门和其他指南)

1、入门文档

在构建 API 服务的时候,你需要明确一些事情,比如:这个 API 是做什么的?如何建立开发者环境?如何测试该服务?如何提交问题?如何部署它?

通常我们可以通过自述(README)文件来回答上面的这些问题,自述文件一般放在你的代码库中,用于为开发者提供使用你项目的最基本的起点和说明。

自述文件应该包含:

  • API 的描述
  • 技术参考与指南的链接
  • 如何以开发者的身份设置你的项目
  • 如何测试这个项目
  • 如何部署这个项目
  • 依赖管理
  • 代码贡献指南
  • 行为准则
  • 许可证
  • 致谢

你的自述文件应该简明扼要;你不必解释每一个方面,但要提供足够的信息,以便开发者在熟悉你的项目后可以进一步深入研究。

2、技术参考

在 REST/JSON API 中, 每一个具体的 端点 endpoint 都对应一个特定的功能,都需要一个具体的说明文档,这非常重要。文档中会定义 API 的描述,输入和可能的输出,并为各种客户端提供使用示例。

OpenAPI 是一个创建 REST/JSON 文档的标准, 它可以指导你完成编写 API 文档所需的各种细节。OpenAPI 还可以为你的 API 生成演示文档。

3、使用方法

对于 API 的用户来说,仅仅只有技术说明是不够的。他们还需要知道如何在一些特定的情况和场景下来使用这些 API,而大多数的潜在用户可能希望通过你的 API 来解决他们所遇到的问题。

向用户介绍 API 的一个好的方法是利用一个“开始”页面。“开始”页面可以通过一个简单的用例引导用户,让他们迅速了解你的 API 能给他们能带来的益处。

总结

对于任何完善的 API,文档都是一个很关键的组成部分。当你在创建文档时,你需要关注 API 文档中的如何入门、技术参考以及如何快速开始等三个方面,这样你的 API 才算是一个完善的 API。

验证

API 开发过程中经常被忽视的一个点就是验证。它是一个验证来自外部来源的输入的过程。这些来源可以是客户端发送过来的 JSON 数据,或者是你请求别人的服务收到的响应数据。我们不仅仅要检查这些数据的类型,还要确保这些数据确实是我们要的数据,这样可以消除很多潜在的问题。了解你的边界以及你能控制的和不能控制的东西,对于 API 的数据验证来说是一个很重要的方面。

最好的策略是在进入数据逻辑处理之前,在你能控制的边界的边缘处进行数据的验证。当客户端向你的 API 发送数据时,你需要对该数据做出任何处理之前应用你的验证,比如:确保 Email 是真实的邮件地址、日期数据有正确的格式、字符串符合长度要求。

这种简单的检查可以为你的应用增加安全性和一致性。还有,当你从某个服务接收数据时,比如数据库或缓存,你需要重新验证这些数据,以确保返回的结果符合你的数据检查。

你可以自己手写这些校验逻辑,当然也可以用像 LodashRamda 这样的函数库,它们对于一些小的数据对象非常有用。像 JoiYupZod 这样的验证库效果会更好,因为它们包含了一些常见的验证方法,可以节省你的时间和精力。除此,它们还能创建一个可读性强的模式。如果你需要看看与语言无关的东西,请看 JSON Schema

总结

数据验证虽然并不显眼和突出(LCTT 译注:跟 API 的功能实现以及其他几个方面比),但它可以帮你节省大量的时间。如果不做验证,这些时间将可能被用于故障排除和编写数据迁移脚本。真的不要相信你的客户端会发送干净的数据给你,也不要让验证不通过的数据渗入到你的业务逻辑或持久数据存储中去。花点时间验证你的 API 收到的数据和请求到的响应数据,虽然在前期你可能会感到一些挫折和不适,但这总比你在后期花大量时间去做各种数据收紧管制、故障排除等要容易得多。

测试

测试是软件开发中的最佳实践,它应该是最主要的非功能性的要求。对于包括 API 在内的任何项目,确定测试策略都是一个挑战,因为你自始至终都要掌握各种约束,以便相应的来制定你的测试策略。

集成测试 Integration testing 是测试 API 的最有效的方法之一。在集成测试模式中,开发团队会创建一个测试集用来覆盖应用流程中的某些部分,从一个点到另一个点。一个好的集成测试流程包括测试 API 的入口点以及模拟请求到服务端的响应。搞定这两点,你就覆盖了整个逻辑,包括从 API 请求的开始到模拟服务器的响应,并返回数据给 API。

虽然使用的是模拟,但这种方法让能我们专注于代码逻辑层,而不需要去依赖后端服务和展示逻辑来进行测试。没有依赖的测试会更加可靠、更容易实现自动化,且更容易被接入持续集成管道流。

集成测试的实施中,我会用 TapeTest-serverFetch-mock。这些库让我们能够从 API 的请求到数据的响应进行隔离测试,使用 Fetch-mock 还可以将出站请求捕获到持久层。

总结

虽然其他的各种测试和类型检查对 API 也都有很好的益处,但集成测试在流程效率、构建和管理时间方面却有着更大的优势。使用 Fetch-mock 这样的工具,可以在服务边界提供一个干净的模拟场景。

专注于基础

不管是设计和构建应用程序还是 API,都要确保包含上面的四个基本要素。它们并不是我们唯一需要考虑的非功能性需求,其他的还包括应用监控、日志和 API 管理等。即便如此,安全、文档、验证和测试这四个基本点,对于构建任何使用场景下的完善 API 都是至关重要的关注点。


via: https://opensource.com/article/21/5/successful-apis

作者:Tom Wilson 选题:lujun9972 译者:ywxgod 校对:wxy

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