真实项目中的一个需求分析、建模、编码、测试过程(一)

本文介绍了一个高考志愿填报分析模块的设计思路,包括模型构建、分析算法选择及其实现方式。通过不同省份、批次和分段的条件,采用Ruby编程实现个性化志愿分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

笔者要完成的,是个《高考志愿填报分析》模块。该模块的输入是用户填报的志愿。输出是业务人员些的一些分析结果。

这里有几个需要说明的:
1 分析算法现在只有三种,分别是:大平行,小平行,和顺序志愿。到底使用哪个分析算法,是根据用户所在的省份、所填报志愿的批次,以及给定批次的分段(可以理解成二级批次,不一定每个省份都有)来定。
经过分析,确定有三个条件:用户所在的省份(用拼音表示)、志愿批次(用1,2,3表示)、分段(可为空,用A,B,Y表示)

2 分析分为两种,一种是针对对某个批次分析,为用户提供该批次的填报意见,另外一种,是对具体填报的学校进行分析,为用户提供该学校(所添专业)的填报意见

3 分析的根据为录取概率。录取概率分为某学校的录取概率,以及某学校某专业的录取概率。录取概率和用户所在省份的预计分数线,以及用户自己预估的分数来决定。因为篇幅有限,这里不作展开。

好,有了需求,就可以建模了。

#某一批次(的某个分段)的志愿
PriorityWish
has_many :college_wishes
belongs_to :user
member :priority_code #批次,代码中使用编码来表示
member :segment #分段,可为空,(A,B,Y)中的一个

#某个学校的志愿
CollegeWish
has_many :major_wish
belongs_to :college
belongs_to :priority_wish
member :position #第几志愿

#某个专业的志愿
MajorWish
belongs_to :major
belongs_to :college_wish
member :position #第几志愿

另外:
College has_many majors

好,模型大致就这样了。

接下来现考虑分析算法的选择。这个我选择了硬编码的方式,Ruby的表达能力强,所以很自然选择了这个方案。
当然,有的人偏向用配置文件/数据库方式。我觉得这两种方式都不直观,对这个这里不讨论。


JudgeCond={
:One=>[:anhui , :hebei, :hainan, :guangxi, :zhejiang,
{:jilin=>{1=>"A"},:guizhou=>1,:sichuan=>1}] ,

:Two=>[:shandong, :shanxi_20, :chongqing, {:guizhou=>[2,3],:sichuan=>[2,3],:jilin=>{2=>"A,B",3=>"all"}}],

:Three=>[:heilongjiang,{:guangxi=>{2=>"Y",3=>"Y"}, :sichuan=>{2=>"Y",3=>"Y"},:jilin=>{1=>"B"}} ]
}

可以看到,:One,:Tow,:Three 分别代表了三种分析策略。

刚才说了,分析有两种,一种是针对某个批次的分析:
class PriorityStrategyBase
member :priority_wish
def analyze
end
end

底下有三种策略。

class PriorityStrategyOne ....
class PriorityStrategyTow ....

另外一种是针对某个学校的填报分析:

class CollegeStrategyBase
member :college_wish

代码的结构就这样。

重头在测试:

1 不用rspec。从java转过来的人,还是很崇拜当初kent设计的 Junit(ruby里就是 test/unit喽)的
2 不用rails的fixtures,丑陋,难以复用,难以保证测试覆盖率,维护代价太大。。(此处省略200字)
3 喜欢Mocha,比java 的jmock好用,美观大方,使用简单,功能也很单纯。
由于精力有限,今天只帖部分测试。等我明天有空,再来个续集。
3 不用windows,windows上跑个测试要等一只烟的功夫。没那个耐心。
(众里寻他千百度,原来要的是Vim,
借问好editor何处有,dazuiba遥指vim,
五岳归来不看山,Vim归来不看簧片
。。。此处省略1000字)

4 看书上说,TDD,这个我还学不来。也没打算尝试。
(TDD的书看了不少,最薄的kent Back的《tdd实践》,可惜例子太简单鸟,不能说明问题)
(说句那心里话,我现在觉得叫喊TDD的又两种人,一种是我崇拜的高手,另外一种跟整天混douban的我一个德行--装X)

6 由于职业限制,只保留必须的实例代码。对于想自己在机器上跑的同学,您失望了,谁让这是真是的项目呢。

7 人家大牛说,好的代码就是注释。我是直接理解成“不些注释”。所以您忍着点,注释,这个‘真没有’
废话不说了。直接上代码。

module Analyze
JudgeCond={
:One=>[:anhui , :hebei, :hainan, :guangxi, :zhejiang,
{:jilin=>{1=>"A"},:guizhou=>1,:sichuan=>1}] ,

:Two=>[:shandong, :shanxi_20, :chongqing, {:guizhou=>[2,3],:sichuan=>[2,3],:jilin=>{2=>"A,B",3=>"all"}}],

:Three=>[:heilongjiang,{:guangxi=>{2=>"Y",3=>"Y"}, :sichuan=>{2=>"Y",3=>"Y"},:jilin=>{1=>"B"}} ]
}

def self.find_province_cond(province,priority_code,segment=nil)
assert province
assert priority_code
province_cond.find{|e|e.accept?(province, priority_code, segment)}
end

def self.province_segments(province, priority_code)
return [] if priority_code<11
priority_id = priority_code-10

province_cond.find_all{|e|
e.province_id==province.id&&e.options.is_a?(Hash)&&(_v=e.options[priority_id])&&_v!="all"
}.map{|e|e.options[priority_id].split(",")}.flatten.sort

end

def self.analyze_college(wish)
assert wish.is_a? Wish
result=Analyze.find_province_cond(wish.user.province, wish.priority_code, wish.segment)
result = result ? result.strategy : :One
"CollegeStrategy#{result}".constantize.new(priority_wish).analyze
end

def self.analyze_priority(priority_wish)
assert wish.is_a? PriorityWish
result=Analyze.find_province_cond(priority_wish.user.province, priority_wish.priority_code, priority_wish.segment)
result = result ? result.strategy : :One
"PriorityStrategy#{result}".constantize.new(priority_wish).analyze
end


def self.province_cond
@province_cond||=ProvinceCond.parse(JudgeCond)
end

def self.find_province(pinyin)
pinyin,id=pinyin.split("_")
province=id ? Array(Province.find(id)) : Province.find_all_by_pinyin(pinyin.to_s)
assert province.size==1
province.first
end

class ProvinceCond < Struct.new(:strategy,:province_id, :options)
def self.parse(hash)
result=[]
hash.each do|element|
element.last.each{|e|
if e.is_a? Hash
result+=e.map{|j|self.new(element.first, Analyze.find_province(j.first.to_s).id, j.last)}
else
assert e.is_a? Symbol
result << self.new(element.first,Analyze.find_province(e.to_s).id,nil)
end
}
end
result.sort_by(&:sort_factor)
end

def accept?(province,priority_code,segment)
return false if province.id!=province_id
return false if segment&&!options.is_a?(Hash)
priority_id = priority_code-10
if options.is_a? Hash
v=options[priority_id]
return false if v.nil?
s=v.split(",")
s.first=="all"||s.include?(segment)
elsif options
Array(options).include? priority_id
else
true
end
end

def sort_factor
2-(options ? (options.is_a?(Hash) ? 2 : 1) : 0)
end
end


class PriorityStrategyBase

attr_reader :priority_wish

def initialize(priority_wish)
@priority_wish = priority_wish
end


def analyze(college_wishes)
#1.1 input
return t 'not_ok' unless priority_wish.over_control_score?
return t 'input_1' if college_wishes[0..1].any?{|e|e.nil?}

input_count = college_wishes.count{|e|!e.nil?}

return t('input_2',:all=>college_wishes.size,:input=>input_count) if college_wishes.size - input_count >=2

#1.2 risk
risk= case college_wishes[0..1].map(&:recruit_possibility).max
when 0...0.3 then t('risk_1')
when 0.3..0.6 then t('risk_2')
else
safeguard? ? nil : t('risk_3')
end

#1.3 order
order = t('order', :order => college_wishes.compact.sort_by(&:recruit_possibility).map{|e|e.college.name}.join(','))

[risk,order,t('sum')]
end

protected

def college_wishes
@priority_wish.college_wishes
end

def t(key,hash={})
I18n.t("strategy_1.priority.#{key}", hash.merge(:default => key.to_sym, :scope=>"analyze"))
end

end

class PriorityStrategyOne < PriorityStrategyBase

end

class PriorityStrategyTwo < PriorityStrategyBase

end

class PriorityStrategyThree < PriorityStrategyBase

end

class CollegeStrategyBase
def analyze(wish_majors)
assert wish_majors.size>1
return t('input_1') if wish_majors[0..1].any?{|e|e.nil?}

risk = major_adjustable? ? nil : t('risk_ajust')

order = [order_reasonable? ? t('order_ok') : t('order_not_ok')]
major_names = wish_majors.compact.sort_by(&:recruit_possibility).map{|e|e.major.name}.join(',')
order << t('order', :order=>major_names)
[risk,order,t('sum')]
end

def order_reasonable?
!wish_majors.any?{|e|e.position>1&&e.recruit_possibility > wish_majors[e.position-2].recruit_possibility}
end

def t(key,hash={})
I18n.t("strategy_1.college.#{key}", hash.merge(:default => key.to_sym, :scope=>"analyze"))
end
end

class CollegeStrategyOne < CollegeStrategyBase
end

class CollegeStrategyTwo < CollegeStrategyBase
end

class CollegeStrategyThree < CollegeStrategyBase
end
end



测试代码:


require "#{File.dirname(__FILE__)}/../test_helper"
class AnalyzeTest < ActiveSupport::TestCase

def test_province_segements
assert_equal [], Analyze.province_segments(Province.find_by_pinyin('anhui') ,11)
assert_equal [], Analyze.province_segments(Province.find_by_pinyin('sichuan') ,11)
assert_equal ["A","B"], Analyze.province_segments(Province.find_by_pinyin('jilin'),11)
assert_equal ["A","B"], Analyze.province_segments(Province.find_by_pinyin('jilin'),12)
assert_equal [], Analyze.province_segments(Province.find_by_pinyin('jilin'),13)
end

def test_find_province_cond
assert_equal :One, find_strategy('anhui',11,nil)
assert_equal nil, find_strategy('jilin',11,nil)
assert_equal :One, find_strategy('jilin',11,'A')
assert_equal :Three, find_strategy('jilin',11,'B')
assert_equal :Three, find_strategy('heilongjiang',11,nil)
assert_equal :One, find_strategy('guangxi',11,nil)
assert_equal :One, find_strategy('guangxi',12,nil)
assert_equal :Three, find_strategy('guangxi',12,"Y")
assert_equal :Two, find_strategy('chongqing',11,nil)

assert_equal :One, find_strategy('guizhou',11,nil)
assert_equal :Two, find_strategy('guizhou',12,nil)
assert_equal :Two, find_strategy('guizhou',13,nil)

assert_equal :Two, find_strategy('shanxi_20',11,nil)
assert_equal nil, find_strategy('shanxi_8',11,nil)

assert_equal nil, find_strategy('guizhou',13,"A")
end

private
def find_strategy(pinyin,code=nil,segment=nil)
s=Analyze.find_province_cond(Analyze.find_province(pinyin),code,segment)
s ? s.strategy : nil
end
end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值