【问题标题】:Simulating aspects of static-typing in a duck-typed language用鸭子类型语言模拟静态类型的各个方面
【发布时间】:2011-02-25 09:09:02
【问题描述】:

在我目前的工作中,我正在构建一套严重依赖对象的 Perl 脚本。 (在哈希上使用 Perl 的 bless() 以尽可能接近 OO)

现在,由于缺乏更好的表达方式,我公司的大多数程序员都不是很聪明。更糟糕的是,他们不喜欢阅读文档,而且似乎很难理解其他人的代码。牛仔编码是这里的游戏。每当他们遇到问题并试图解决它时,他们都会想出一个可怕的解决方案,但实际上什么都解决不了,而且通常会使情况变得更糟。

坦率地说,这导致我不相信他们用鸭子类型语言编写的代码。举个例子,我看到太多的问题是他们没有因为滥用对象而没有得到明确的错误。例如,如果类型A 具有成员foo,并且他们执行instance->goo 之类的操作,他们不会立即发现问题。它将返回一个空值/未定义值,他们可能会浪费一个小时来寻找原因。然后最终更改其他内容,因为他们没有正确识别原始问题。

所以我正在集思广益,寻找一种方法来保留我的脚本语言(它的快速发展是一个优势),但在对象未正确使用时给出明确的错误消息。我意识到,由于没有编译阶段或静态类型,因此错误必须在运行时出现。我没问题,只要用户收到一个非常明确的通知说“这个对象没有 X”

作为我的解决方案的一部分,我不希望他们在尝试使用方法/变量之前检查它是否存在。

尽管我的工作是使用 Perl,但我认为这可能与语言无关。

【问题讨论】:

  • 鉴于您处于这种环境中,用 Perl 编写代码,同时承认您的同事不理解它……听起来您是问题的一部分!
  • 我没有选择使用 Perl。根据管理层的说法,要么是这个,要么是 sh。
  • 我处于某种领导地位,但官僚机构会做出技术决策。我正在努力改变这一点,但我仍然没有我需要的影响力。
  • 听起来你有一个社会问题,你正试图用技术来解决。该技术将在短期内有所帮助,但除非您教育您的同事,而不是与您合作,否则他们可能会反感这些限制,将其视为障碍,并绕过它们。
  • 你的意思是你想让$instance->{member} 抛出一个错误,这样程序员就不会抓住对象的内脏和内部使用的访问器方法?

标签: perl language-agnostic duck-typing static-typing


【解决方案1】:

您可以使用Class::InsideOutObject::InsideOut,它们为您提供真正的数据隐私。不是将数据存储在祝福哈希引用中,而是使用祝福标量引用作为词法数据哈希的键。长话短说,如果您的同事尝试$obj->{member},他们会收到运行时错误。 $obj 中没有任何东西可供他们获取,也没有简单的方法可以获取数据,除非通过访问器。

这里是a discussion of the inside-out technique and various implementations

【讨论】:

  • 我记得几年前有很多关于由内而外的物体的讨论,它们似乎是新的热点,但这些天它们几乎已经淡出了人们的视线。知道他们为什么失宠吗?他们是否存在实际问题,或者他们只是被 Moose 黯然失色?
  • AFAIK 使它们成为问题的大多数问题都已解决,这是 5.10(已向后移植到 5.8)的一个重要功能,以添加字段哈希(请参阅 Hash::Util::FieldHash)适当地支持他们。也许现在实施问题已经解决了,不再值得讨论?
【解决方案2】:

在 Perl 中,

  • 要求 use strictuse warnings 在 100% 的代码中打开

  • 您可以尝试通过创建closures 来制作几乎私有的成员变量。一个很好的例子是http://www.usenix.org/publications/login/1998-10/perl.html 中的“私有成员变量,排序”部分。它们不是 100% 私密的,而是相当不明显的访问方式,除非您真的知道自己在做什么(并要求他们阅读您的代码并进行研究以找出方法)。

  • 如果你不想使用闭包,下面的方法效果不错:

    将所有对象成员变量(在 Perl 中也称为对象哈希键)封装在访问器中。有一些方法可以通过编码标准 POV 有效地做到这一点。最不安全的方法之一是 Class::Accessor::Fast。我确信 Moose 有更好的方法,但我对 Moose 并不熟悉。

    确保在私有约定名称中“隐藏”实际成员变量,例如$object->{'__private__var1'} 是成员变量,$object->var1() 是 getter/setter 访问器。

    注意:最后,Class::Accessor::Fast 不好,因为它的成员变量与访问器共享名称。但是您可以拥有非常简单的构建器,它们就像 Class::Accessor::Fast 一样工作,并为“foo”创建诸如 $obj->{'__private__foo'} 之类的键值。

    这不会阻止他们朝自己的脚开枪,但会让这样做变得更加困难。

    在你的情况下,如果他们使用 $obj->goo$obj->goo(),他们会得到一个运行时错误,至少在 Perl 中是这样。

    他们当然可以不遗余力地做$obj->{'__private__goo'},但是如果他们因为纯粹的懒惰而做奇闻趣事的牛仔废话,那么后者比做正确的$obj->foo()要多得多。

    您还可以扫描检测$object->{"_ 类型字符串的代码库,但从您的描述来看,这可能不会起到太大的威慑作用。

【讨论】:

  • @jrockway - 很公平 - 我的措辞不好。我将删除 exaggregationrion。但正如我在“私人姓名”选项中所说,游戏的名称是“做坏事比做好事更难”。
【解决方案3】:

如果您想添加要使用的模块,请尝试Moose。它提供了您在现代编程环境中所需的几乎所有功能,等等。它进行类型检查、出色的继承、具有自省功能,并使用MooseX::Declare,这是 Perl 类最好的接口之一。看看:

use MooseX::Declare;

class BankAccount {
    has 'balance' => ( isa => 'Num', is => 'rw', default => 0 );

    method deposit (Num $amount) {
        $self->balance( $self->balance + $amount );
    }

    method withdraw (Num $amount) {
        my $current_balance = $self->balance();
        ( $current_balance >= $amount )
            || confess "Account overdrawn";
        $self->balance( $current_balance - $amount );
    }
}

class CheckingAccount extends BankAccount {
    has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );

    before withdraw (Num $amount) {
        my $overdraft_amount = $amount - $self->balance();
        if ( $self->overdraft_account && $overdraft_amount > 0 ) {
            $self->overdraft_account->withdraw($overdraft_amount);
            $self->deposit($overdraft_amount);
        }
    }
}

我觉得这很酷,我自己。 :) 它是 Perl 对象系统之上的一个层,所以它适用于你已经拥有的东西(基本上)。

使用 Moose,您可以非常轻松地创建子类型,因此您可以确保您的输入是有效的。懒惰的程序员同意:在 Moose 中使子类型工作要做的事情很少,做起来比不做要容易! (来自Cookbook 4

subtype 'USState'
    => as Str
    => where {
           (    exists $STATES->{code2state}{ uc($_) }
             || exists $STATES->{state2code}{ uc($_) } );
       };

还有 Tada,USState 现在是您可以使用的类型!无需大惊小怪,只需少量代码。如果它不正确,它会抛出一个错误,你类的所有消费者所要做的就是传递一个带有该字符串的标量。如果没问题(应该是……对吗?:))他们像往常一样使用它,并且您的班级免受垃圾的侵害。多好啊!

Moose 有很多类似的东西。

相信我。看看这个。 :)

【讨论】:

  • Moose 有办法将 $object->{member} 变成错误吗?我认为这就是 OP 想要的。
  • 查找 MooseX::InsideOut。它应该使 $object->{member} 无效,同时仍然能够使用上述所有的鹿角优点。
猜你喜欢
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 2010-12-30
  • 1970-01-01
  • 1970-01-01
  • 2011-03-23
  • 1970-01-01
相关资源
最近更新 更多