论坛首页 综合技术论坛

付出太多,得到太少 - 闲谈函数返回值

浏览 17573 次
精华帖 (1) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-09-07  
模仿这个:理论上来说,对象是否应该具备自杀的能力?

把技术话题放海版,扯起来轻松一点。

我的问题是:函数返回值太单一

一个函数的名字表明了它的功能,它的输入可以很灵活,多个参数,参数可以包含复杂的数据结构,有的语言还支持参数缺省值、keyword形式,重载还能在不改变函数名的前提下让输入更灵活,this/全局变量其实也算输入。

返回值的形式就太单一了,只能return固定类型的单个值。

举个例子。

给用户分配任务:user.assignTask(task)

(一)我希望可以这样用:
boolean success = user.assignTask(task);
if(success){
  ....
}else{
  display("Task assignment failed"); 
}


(二)还能这样用:
AssignResult result = user.assignTask(task);
if(result.success){
 ....
}else{
  display(result.errors);
}


(三)还能这样:
try{
  user.assignTask(task);
  ....
}catch(AssignException ex){
  display(ex.errors);
}


(四)还能:
user.assignTask(task1).assignTask(task2);



假定assignTask的内部逻辑很复杂。

讨论不限java。
   发表时间:2008-09-07  
引用
返回值的形式就太单一了,只能return固定类型的单个值。

那是静态类型语言才存在的
比如Ruby的返回值就不需要是固定类型的

但无论如何你需要“知道”什么将被返回
所以,我不明白为什么你“想要”这些东西
0 请登录后投票
   发表时间:2008-09-07  

即使是静态语言,也可以返回 数组,集合,HashMap.

想返回啥,就返回啥。
0 请登录后投票
   发表时间:2008-09-07  
#include<iostream>
using namespace std;
#define boolean int
class AssignException{
public:
int errors;
};
class AssignResult{
public:
int n;
};
class u{
public:
int n;
operator boolean (){return n;}
operator AssignResult(){AssignResult a;a.n=n;return a;}
u assignTask(int task);
};
u u::assignTask(int task)
{
u uu;
AssignException a;
a.errors=1111;
if(task==0)throw a;
uu.n=2222;
return uu;
}
int main()
{
u uu,uu2;
AssignResult a;
boolean b;
a=uu.assignTask(1);
cout<<a.n<<endl;
b=uu.assignTask(1);
cout<<b<<endl;
uu2=uu.assignTask(1).assignTask(2);
cout<<uu2.n<<endl;
try{
uu.assignTask(0);
}catch(AssignException ex){ 
cout << ex.errors<<endl; 
}
return 0;
}
0 请登录后投票
   发表时间:2008-09-07  
Object[] assignTask(Task);

V assignTask(T)
0 请登录后投票
   发表时间:2008-09-08  
这是砣伪需求,鉴定完毕
0 请登录后投票
   发表时间:2008-09-08  
gigix 写道
但无论如何你需要“知道”什么将被返回
所以,我不明白为什么你“想要”这些东西


返回了true/false,就不能返回更详细的信息,也不能返回taskId,或返回user。
返回了数组,就不能把它直接当成taskId用。
抛出了异常,就不能返回值。

这就是问题。


对前面的用法编了号。

再加一个(用法五):
int taskId = user.addTask(task);

得到新创建的taskId。

解释一下用法四和其它用法的区别: 只有调用者用try catch包起来时,出错才会抛异常。否则,出错只是普通返回。

ruby对用法四的惯例是 user.add_task!(task)
这样,出错后,不返回true/false,而是raise Exception。
这本质是加了名为"add_task!"的另一个函数,并没有从语言层面入手。

对这个惯例的解读:调用方不但说明了被调函数的语意(add_task),同时说明了调用者的意图和使用方式(!号)。

技术上,我们用参数、this、重载、多态等方式,把输入的多样性充分的拆解了。然而,输出的多样性呢?

这个惯例,隐含了如下信息:在函数的数据输出和处理方式上,是多样的,也有一定的模式可循。如我前面列出的,或者判断成功失败,或者取操作详细信息,或者捕获异常,或者直接使用返回的业务数据,或者链式调用。

相信很多人在设计系统接口时,对采用哪种“输出模式”头痛吧?

在我看来,异常,是为了弥补返回值类型单一和用法单一的问题,引入的另一种返回值形式。参考java的checked exception在方法签名上的表现。

以这个思路,用法一和用法二,可以约定这样调用
user.assign_task?(task)


assign_task?返回一个通用的Result实例,并象ddd给出的例子,让它能够“类型协变” - 可能用错词了,总之能把它当作boolean使用,也能从中取到errors等信息。

再进一步,比如,add_task的内部实现里,一些关键性操作产生的中间数据,能不能像errors一样,以“可选返回值”的方式提供出来?(既然有可选输入参数)
ruby/python中,返回数组,再用平行赋值的方式接收,部分解决了这个问题。但同时改了“主返回值”类型(改成了数组),不完美。

这些工作,能不能再深入一点,在语言层做呢?
只定义一次add_task,就有了add_task!和add_task?(这一点,动态语言很接近了)
把Result类型作为语言层面的东西,自动封装函数返回值,或者直接作为Object的一个特性,可以类型协变成“主返回值”,支持“可选返回值”,响应!?等调用者抽象意图。
看前面五种用法,只有用法五的返回值代表了业务数据(taskId,作为主返回值),其它四种都是对操作过程的描述(操作成败,错误信息,作为可选返回值)。

对于静态语言,能不能利用编译器,实现“返回值重载”呢?(既然有参数重载)
根据接收变量的类型,返回正确的值。让这两行都好使:
boolean success = user.addTask(task);

int taskId = user.addTask(task);


底下的事,编译器帮咱干了!~ 咱这叫"result pattern match",或"result type inferred"

我所了解的type inferred实现,只是承认并接受了返回值信息不够丰富的事实,并没有试图打破它。
有没有可能两者结合?


关于用法四的链式调用,多数语言是靠设定返回值为this实现的(return this)。这样就不能返回其它东西了。
能不能另辟蹊径,比如通过其它操作符:
user->assignTask(task1)->assignTask(task2);

再比如OGNL Chained Subexpressions的形式:
user.(assignTask(task1),assignTask(task2))

参考:
http://www.ognl.org/2.6.9/Documentation/html/LanguageGuide/chainedSubexpressions.html

我爱OGNL~~~


输出数据不丰富,也导致很多人用实例变量进行中转。--如果不遵循Command等模式的潜在约定,就会引发逻辑问题,或使代码的可维护性变差。

总结一下:
1。 输出数据 范式。
2。 调用者处理方式 范式。

俺这是信马由缰的头脑风暴,所以丢到了海版。在默念master乌龟的话:
引用

Quit? Don't quit?
Noodles? Don't noodles?


在现有语言设施上,怎么处理这些问题呢?
0 请登录后投票
   发表时间:2008-09-08  
这个问题8素函数的问题,也8素静态类型的问题
而素静态OO的问题.
若不考虑OO,静态类型语言只要Type inference就可以搞定鸟...

但素,静态OO里方法是类滴成员.方法可以素虚滴,或者像Java那样所有的方法都素虚滴.因此如果出现了虚函数的继承问题,就必须在运行期解决。所以如果在编译器做result interfernce滴话,虚函数就完蛋鸟.



0 请登录后投票
   发表时间:2008-09-08  
异常状态代码 方法名(输入参数1,输入参数2,(。。。。。多个)侦听参数,输出参数1,输出参数2(。。。。多个));


这样子设计方法么?
0 请登录后投票
   发表时间:2008-09-08  
直接上代码~
没有考虑NilClass,FixNum;
错误重置做的很粗糙:

class Object
  def carry(hash={})
    reset_carriage
    metaclass = (class << self; self; end)
    metaclass.send(:define_method,:success?) do
      hash.empty?
    end
    
    metaclass.send(:define_method,:errors) do
      hash[:errors]
    end
    self
  end

  def reset_carriage
    metaclass = (class << self; self; end)
    if self.respond_to?('errors') # @_@
      metaclass.send(:remove_method, :success?)
      metaclass.send(:remove_method, :errors)
    end
    self
  end
 
  def success?
    true
  end
end

class Class
  def exclamation(*methods)
    methods.each do |m|
      self.send(:define_method,"#{m}!") do |*args|
        result = self.send(m,*args)
        if result.success?
          return result
        else
          raise Exception.new(result.errors)
        end
      end
    end
  end
end

class Task
  attr_accessor :name
  def initialize(name)
    @name = name
  end
end

class User
  attr_accessor :tasks
  def initialize
    @tasks = []
  end

  def assign_task(task_name)
    if task_name == 'critical task'
      return nil.carry(:errors => "can't assign critical task to him!")
    end
    
    task = Task.new(task_name)
    @tasks << task
    return task
  end
  exclamation :assign_task
end

user = User.new
task1 = user.assign_task("task1")
puts task1 # #<Task:0xb7df9b54>(newly created instance)
puts task1.success? # true

puts '-------'

critical_task = user.assign_task("critical task")
puts critical_task # nil
puts critical_task.success? # false
puts critical_task.errors # can't assign critical task to him!

puts '--------'

task2 = user.assign_task!("task2")
puts task2 # #<Task:0xb7df98d4>(newly created instance)
puts task2.success? # true

puts '-------'

critical_task = user.assign_task!("critical task") # will raise Exception

0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics