【问题标题】:Null object pattern for functional paradigm功能范式的空对象模式
【发布时间】:2018-03-09 09:54:28
【问题描述】:

将 Typescript 用于 Angular ngrx 应用程序,我必须做很多检查,我访问或传递的值是否为空/未定义,例如在可能存在的列表上调用 .map()。

要走的路是什么?确保整个代码库永远不会移交空值?在每次通话之前检查,以防生成空列表之类的空对象?还是有某种类似空对象的函数式编程模式?

@Pavel 的评论: 我们有一个可以选择的项目列表,如果是这样,详细信息会在视图中展开。我碰巧在删除列表中打开的项目时,应用程序崩溃了,因为对打开项目的引用设置为 null。我通过对我为修复应用程序崩溃而编写的所有选择器进行空检查来解决此问题,正如我在许多其他选择器中看到的那样,这些选择器在浏览器中使用应用程序本地状态。

【问题讨论】:

  • 你能提供一些例子吗?
  • 对于列表,最好返回空而不是空。对于其他类型,尽量不要使用 null 而使用 undefined 。只是避免将其用于有效性。您可以启用 --strictNullChecks(仅当 angular >= 5 时)。
  • @JEY 我想过“只是不要使用 null - 所以不需要防御性编程。但我怀疑我能确保这一点,因为角度和异步。尽管我喜欢这个想法快速找不到错误,我希望我的应用程序不会崩溃。将看看 strictNullChecks

标签: typescript functional-programming null-object-pattern


【解决方案1】:

这里有几个众所周知的模式可以提供帮助。

选项类型,又名 Maybe

Maybe/Option 是一个 sum 类型,在 TypeScript 中具有以下结构:

type Maybe<T> = T | void;

您可以在 typescript user manual 中阅读有关实现它的更多信息,只需 ctrl-f 表示“可能”,因为它们似乎出于某种原因避开了片段标识符。

装饰器

Maybe(其中之一)的问题在于它很快变成了病毒,它接管了你的整个代码库并且每个值都变成了一个 Maybe。如果没有模式匹配,处理这个特别烦人(打字稿手册中的示例非常冗长)。恕我直言,一个更好的选择,因为 JavaScript/TypeScript 缺少任何类型的存在运算符,所以编写你的函数而不进行空检查,然后用进行检查的函数装饰它们。

function argCheckDecorator(f) {
  return function(...args) {
    // Functions have a length property that is their arity.
    // You could modify this to only check the first argument,
    // not check arity for varargs, etc.
    if (
      args.length === f.length &&
      args.every(arg => arg !== null && arg !== undefined)
    ) {
      return f.apply(this, args);
    } else {
      // optionally warn console
      // console.warn(`Called ${f.name} with invalid arguments ${args}.`);
      return null;
    }
  }
}https://www.destroyallsoftware.com/talks/boundaries

这里明显的问题是您正在执行相当广泛的运行时检查(AFAIK 是导致您首先提出问题的原因),而不是依赖编译器。不幸的是,在 JavaScript/TypeScript 中,您的选择总是有限的,无法保证对 DOM 的调用永远不会返回 null 或者对象属性访问永远不会返回 undefined(至少如果您是例如,将 JSON HTTP 响应解析为对象)。

至少使用函数装饰器,您可以将空值检查从每个函数中移出。将我的高阶函数变成 TypeScript 装饰器留给读者作为练习。

更新

举个例子说明我在 cmets 中所说的内容:

// messy-shell.js
// All DOM mutation, AJAX calls, optional params, null checks, etc.
// go here.
import * as ideal from './perfect-world.js';
const getDOMElement = (selector, element=document) => {
  return (selector && element instanceof HTMLElement) ?
    ideal.getDOMElement(selector, element) : null;
};

const getAJAXData => (url, method='GET', params) => {
  let p = params ? // IRL you'll want to do more checking than this
    fetch(url, { body: params, method }) :
    fetch(url, { method });

  return p.then(resp => {
    if (resp.statusCode >= 200 && resp.statusCode < 400) {
      return resp.json();
    } else {
      throw new Error(`${resp.statusCode} response.`);
    }
  }).then(data => {
    if (data && (data.length || Object.keys(data).length)) {
      return ideal.processAJAXData(data);
    } else {
      throw new Error('Empty data response.');
    }
  }).then(processedData => {
    // update DOM here, or skip this and just return Promise<processedData>
  }).catch(err => {
    // do error handling
  });
};

同时,回到牧场……

// perfect-world.js
// Assumes that no args are ever omitted, nothing ever
// throws (catching is up to the caller). Functions in this
// file may also call each other, but with caution. Any function
// that uses the result of something that might fail go in messy-shell.
// Functional, in the Functional Programming sense.
export const getDOMElement = (selector, element) => {
  return element.querySelector(selector);
};

// NOTE: knows nothing of Promises, try/catch, JSON.parse, etc.
// Doesn't mutate the DOM either, just processes server response.
export const processAJAXData = data => {
  return Object.entries(data).forEach(datum => {
    // do stuff.
  });
};

现在您不一定希望每个小功能都使用这种级别的仪式,但如果您的团队/代码库/问题域足够大,您可能会这样做。一些不错的属性不在此范围内:

  1. 理想世界中的东西很容易测试。
  2. 理想世界中的东西可以很容易地进行静态分析。
  3. 理想世界中的东西是整洁的,并且在很大程度上是自我记录的

您可能还想查看this

【讨论】:

  • 关于“无法保证对 DOM 的调用永远不会返回 null”。我自己考虑过这个问题,就我实际使用的代码而言,这是真的,但是真的不可能达到无空/干净状态吗?显而易见的答案:不可能的原因 Angular/Async。使用ngrx,状态和浏览器本地的所有内容是否可以保持干净?
  • @Lex 本身与 Angular 或您选择的堆栈的任何其他部分无关,它是 Web 编程(以及一般编程)的本质所固有的。编码类型系统中值的缺失(例如,使用 Maybe)背后的想法不是消除它,而是捕捉程序员没有明确处理这种可能性的任何情况。一种功能性方法(也是我推荐的方法)是尽可能多地编写程序的核心,以生活在一个完美的世界中,该世界有一个薄薄的外壳,可以与凌乱的现实世界接口并调用
  • 纯核心根据需要。无论哪种方式,没有类型系统可能永远能够确保 HTTP 请求永远不会失败。
猜你喜欢
  • 2016-01-19
  • 2020-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-20
相关资源
最近更新 更多