跳转至

已定义变量的稳健检测方法

原文链接: https://www.nv5geospatialsoftware.com/Learn/Blogs/Blog-Details/robust-detection-of-defined-variables

12323 评价本文:

尚未评分

已定义变量的稳健检测方法

匿名作者 2014年9月11日,星期四

最近在为即将发布的ENVI 5.2 API工作时,我开始思考如何正确验证输入参数。编写健壮的ENVI API时,我的目标之一是识别所有无效输入,然后提供有用的错误信息来指导用户修正输入。最常见的做法是对变量名调用N_Elements函数,并与0进行比较(因为所有偶数在未设置compile_opt logical_predicate时都被视为逻辑假值):

if (N_Elements(bands) gt 0) then ...

在编写单元测试套件时,我发现了一种可以干扰此类测试的方法。任何继承自IDL_Object的类都可以重写_overloadSize()方法,这会改变N_ElementsSize(/DIMENSION)返回的值。新的Hash类和List类正是这样做的,对比NullObject和Hash对象的结果就能看出:

IDL> o = Obj_New()

IDL> N_Elements(o)

      1

IDL> Size(o)

      0         11          1

IDL> Size(o, /DIMENSIONS)

      0

IDL> h = Hash()

IDL> N_Elements(h)

      0

IDL> Size(h)

      0         11          0

IDL> Size(h, /DIMENSIONS)

      0

如果用户传入空的Hash或List对象,上述N_Elements测试将不会按预期工作。

我尝试的第二种方法是使用ISA函数来识别非未定义的变量:

if (ISA(bands)) then ...

但这条规则至少也有两个例外。如果对NullObject或NullPointer调用ISA(),它会返回0而不是我期望的1,尽管它能检测到空的Hash或List对象:

IDL> print, ISA(Obj_New())

0

IDL> print, ISA(Ptr_New())

0

IDL> print, ISA(Hash())

1

IDL> print, ISA(List())

1

我的第三种方法是使用Typename函数,这确实符合要求:

if (Typename(bands) ne 'UNDEFINED') then ...

如我们所见,此方法基于一个事实:任何未定义的变量都将返回字符串'UNDEFINED',而其他任何变量都会返回不同的字符串。

IDL> print, Typename(Obj_New())

OBJREF

IDL> print, Typename(Ptr_New())

POINTER

IDL> print, Typename(Hash())

HASH

IDL> print, Typename(List())

LIST

IDL> print, Typename(foo)

UNDEFINED

IDL> print, Typename(!null)

UNDEFINED

IDL> print, Typename([])

UNDEFINED

IDL> print, Typename({})

UNDEFINED

这种方法的缺点是使用字符串比较,效率不如单个布尔变量的比较。更好的替代方案是使用Size(/TYPE),它会返回IDL的整数类型码之一:

if (Size(bands, /TYPE) ne 0) then ...

这是简单的整数比较。现在它的行为符合我们的期望:

IDL> print, Size(Obj_New(), /TYPE)

     11

IDL> print, Size(Ptr_New(), /TYPE)

     10

IDL> print, Size(Hash(), /TYPE)

     11

IDL> print, Size(List(), /TYPE)

     11

IDL> print, Size(foo, /TYPE)

      0

IDL> print, Size(!null, /TYPE)

      0

IDL> print, Size([], /TYPE)

      0

IDL> print, Size({}, /TYPE)

      0

如你所见,有多种方式可以表达一个未定义的变量,这可能是个问题。当函数调用中未使用特定关键字时,它确实是未定义的;同样,如果为该关键字传入的变量从未被设置,也是未定义的。但我展示的最后三种情况是表达空变量的不同方式:显式的!null字面量、空数组语法[]以及空结构体语法{}。后两种看起来有些奇怪,但它们在IDL 8.X中有其用武之地。在IDL 7.1及更早版本中,常看到这样的代码:

outData = [0]

for i = 0, N_Elements(inData)-1 do begin

outData = [ outData, inData[i] ]

endfor

outData = outData[1: *]

我们必须创建一个虚拟数组元素来拼接,然后去掉第一个元素。从IDL 8.0开始,这变得简单一些:

outData = []

for i = 0, N_Elements(inData)-1 do begin

outData = [ outData, inData[i] ]

endfor

空结构体构造可用于重复调用Create_Struct()来追加新标签。在任何一种情况下,如果for循环没有执行,我们就得到一个空数组或空结构体。

随着List和Hash的出现,当容器对象没有元素时,我们也可以分别通过它们的ToArray()ToStruct()方法获得空数组或空结构体。由于ENVI Services Engine接受包含JSON输入的REST请求,我们最终可能会得到空的Hash和List对象。

你可能会问,谁在乎呢,!null或空数组就像未定义的关键字一样可以忽略,在许多情况下这是一个合理的假设。但有时我们希望区分!null和未定义,而上述测试都无法实现这一点。实际上,我们在enviTask中就是这样做的,调用oTask.ParamName=!null会取消设置该参数的值,并允许返回其默认值(如果已设置)。实现这一点的方法是使用ISA函数的/NULL关键字:

IDL> ISA(Obj_New(), /NULL)

0

IDL> ISA(Ptr_New(), /NULL)

0

IDL> ISA(Hash(), /NULL)

0

IDL> ISA(List(), /NULL)

0

IDL> ISA(foo, /NULL)

0

IDL> ISA(!null, /NULL)

1

IDL> ISA([], /NULL)

1

IDL> ISA({}, /NULL)

1

综上所述,验证“未定义”与“空值”与“非空值”的最佳方法是:

if (Size(val, /TYPE) eq 0) then begin

...

endif else if (ISA(val, /NULL)) then begin

...

endelse

回忆科罗拉多洪水:一年后 数据融合的三大应用