• 发生时间:2022-04-24
  • 问题描述:在调试某个项目的功能测试工控板与上位机的通信时,修改上位机的 isDebugMode 参数为 true 后,工控板报错,不会进入自动检测。
  • 问题类别:软件开发
  • 原因分析:
    • 在 JavaScript 代码中误用 keyword arguments 的方式来传参,即isDebugMode=true,而 JavaScript 并不支持这一语法,会导致本该赋给 isDebugMode 的值误赋给 configPath,进而导致配置文件路径有误。
    • 因此,当工控板向上位机请求配置文件时,上位机无法响应,导致工控板报错。(详见下文「原因分析详解」)
  • 解决方案:取消 keyword arguments 的传参方式,改用 positional arguments 的传参方式。
  • 实施结果:修改后工控板可以正常收到配置文件,并继续后面的自动检测了。
  • 经验总结:
    1. Python 支持 keyword arguments 语法,但 JavaScript、Golang 和 C/C++ 等语言都不支持,函数传参时要注意这一点。
    2. 不同编程语言的语法是有差异的。平时开发调试时,遇到这些不同点,特别是容易踩坑的地方,应该及时归纳总结,举一反三。

原因分析详解

上位机定义了客户端类 Client ,用来跟工控板通信,这个类的构造函数签名如下:

  constructor(serialport, configPath = DefaultConfigPath, isDebugMode = false) {}

出现问题时,创建客户端的代码如下:

  client = new Client(serialport, isDebugMode = true);

问题在于,JavaScript 并不支持 keyword arguments 的语法,因此以上代码的实际效果是:

isDebugMode 的取值实际上是赋给 YunganClient 的第二个参数 configPath, 因此 configPath 的值为 true,自然无法读取配置文件,并引发后面的通信异常错误。

keyword arguments 语法简介

Python 官方术语表 的「argument」术语对「keyword argument」(「关键字参数」)和「positional argument」(「位置参数」)进行了介绍:

Argument: A value passed to a function (or method) when calling the function. There are two kinds of argument:

  • keyword argument: an argument preceded by an identifier (e.g. name=) in a function call or passed as a value in a dictionary preceded by **. For example, 3 and 5 are both keyword arguments in the following calls to complex():

    complex(real=3, imag=5)
    complex(**{'real': 3, 'imag': 5})
    
  • positional argument: an argument that is not a keyword argument. Positional arguments can appear at the beginning of an argument list and/or be passed as elements of an iterable preceded by *. For example, 3 and 5 are both positional arguments in the following calls:

    complex(3, 5)
    complex(*(3, 5))
    

不同语言对 keyword arguments 的支持情况

最后我们来调查一些常见语言对 keyword arguments 的支持情况吧。 我们统一定义一个简单的函数,其功能是打印某个人的名字、地址和年龄。

Python:支持 keyword arguments

def show_person(name, addr="Beijing", age=18):
    print(f'name={name}, addr={addr}, age={age}')

# 打印结果符合预期: name=guojing, addr=Beijing, age=20
show_person('guojing', age=20)

JavaScript:不支持 keyword arguments,误用大概率会出现参数被错误赋值的问题

function show_person(name, addr='Beijing', age=18) {
  console.log(`name=${name}, addr=${addr}, age=${age}`)
}

// 打印结果不符合预期: name=guojing, addr=20, age=18
show_person('guojing', age=20)

C++:不支持 keyword arguments,编译会报错

#include <iostream>
#include <string>

using namespace std;

void show_person(string name, string addr = "Beijing", int age = 18) {
  std::cout << "name: " << name << ", addr: " << addr << ", age" << age
            << std::endl;
}

int main() {
  show_person("guojing", age = 20);
}

编译以上代码会报错:

along:~/src/default-arguments$ g++ default_argument.cpp
default_argument.cpp: In function ‘int main()’:
default_argument.cpp:12:26: error: ‘age’ was not declared in this scope
   12 |   show_person("guojing", age = 20);
      |

Golang:不支持 keyword arguments,编译会报错

package main

import "fmt"

func showPeople(name string, addr string, age int) {
  fmt.Printf("name: %s, addr: %s, age: %d\n", name, addr, age)
}

func main() {
  showPeople("guojing", age = 20)
}

编译以上代码会报错:

along:~/src/default-arguments$ go build default_argument.go
# command-line-arguments
./default_argument.go:10:28: syntax error: unexpected =, expecting comma or )

另外,特别注意:Golang 也不支持默认函数参数!