精华内容
下载资源
问答
  • 寻找客户源的方法
    千次阅读
    2021-05-28 15:26:44

    2020年的新手小白,岁数在20岁左右吧。初入社会或者刚接触销售,没有这些资源,没有关系。打电话,不说现在国家管理严格,我们被电话骚然的太多了。避之不及。

    陌生拜访,线下找,很难 触及到领导层。找得到的也是小领导而已。毛头小子不会得到多少的社会资源。

    但是!年轻人,有一项天然的技能!就是:网络,上网!他们是网络的原著民。玩互联网比谁都厉害。所以,我感觉作为销售行业,是时候更新一下脑子,切换一下互联网思维了。

    2020年,再不懂的利用互联网做事情,基本就被这个社会淘汰了!

    传统思维,求别人给自己一次机会,而互联网思维,靠的是吸引,靠的是实力的展示,你喜欢你来找我,做不做你的生意我说了算。

    很多传统企业为什么近些年,客户流失严重?甚至自己都不知道客户怎么流失的。其实你的客户,都是通过网络被别人翘走的。

    举例子来说:a总想买一台机器设备,他去百度搜索:北京哪里有卖****设备的? 北京哪里的*****设备性价比高。

    这时候,搜到的信息可能直接左右他的决定。

    a总有一天没事刷抖音,刷头条,被推荐到一条视频,是北京地区的某公司在介绍自己的设备,正好是他需要的。可能直接联系购买了。

    为什么会这样?

    因为,网络的东西,更形象立体,时效性更强。沟通性也更强。别人会在第一时间获取到这个机会。不会等你的业务员找上门的。

    现在互联网都是个性化推荐算法,如果你在网上搜索过某产品,全网各个平台都会推荐类似的产品内容给你。

    所以,作为一个年轻人,完全可以足不出户,宣传自己。年轻人,有活力,做视频,发抖音,头条。一条视频阅读量几千几万,几十万。能瞬间让几十万人看到,认识到你的产品。哪个方式能匹敌?

    一、通过互联网搜索引擎获取客户资源

    对于新销售员,网上找客户是开始的最好选择,先在网上通过一些商业网站去搜索一些你客户的资料。或通过大型的搜索引擎诸如:百度、用关键词搜索;不要固定用一个搜索引擎,同样的关键词,在不同的搜索引擎搜就有不同的结果。

    二、通过行业分类信息网站找客户

    每个行业都有属于自己的行业网站,潜在客户也比较几种,可以通过行业分类查到相应的公司联系信息,也可以发布一些求购信息。

    更多相关内容
  • 摘要:Java源码,初学实例,IDL Java IDL使用实例代码,通过例子你将了解如何引入要使用的包,本应用的stub类,要使用CORBA的名字服务,使用CORBA服务,声明客户应用类,建立ORB对象,args为客户程序启动时的命令行...
  • 关于客户(销售员该如何找客户才好) 1、旧时指以租佃为生的人家。2、旧时指外地迁来的住户。(以上两条详见下文)。3、工厂企业或经纪人称...教你怎样寻找客户源(销售员该如何找客户才好) 刚入行的销售员,首先要

    关于客户(销售员该如何找客户才好)

    1、旧时指以租佃为生的人家。2、旧时指外地迁来的住户。(以上两条详见下文)。3、工厂企业或经纪人称来往的主顾;客商。4、在网络的通信方式中,指服务的请求方。5、在客户关系管理中,客户是对企业产品和服务有特定需求的群体,它是企业经营活动得以维持的根本保证。6、客户是服务请求方。

    教你怎样寻找客户源(销售员该如何找客户才好)

    刚入行的销售员,首先要面对客户在哪里的问题?就是干了多年的销售员遇到新的产品,特别是新出现的产品,有时连行业都分不清楚。比如:我做了十多年的销售,当开始做品牌的客户关系管理系统这个项目时,就拿不准是要去哪里找代理商渠道,后来通过向同行的学习,探讨慢慢的摸索出了方向。还有就是随着科技的发展,一些跨行业,综合性能的产品会越来越多,渠道方向就涉及到好多路径,同时还要考虑到哪方面的渠道更适合等方面因素,这就为销售员提出了新的问题。那么,我们在寻找客户的过程中应注意些什么呢?

    一、普遍寻找法

    这种方法也称逐户寻找法或者地毯式寻找法。其方法的要点是,在业务员特定的市场区域范围内,针对特定的群体,用上门、邮件或者电话、电子邮件等方式对该范围内的组织、家庭或者个人无遗漏地进行寻找与确认的方法。比如,将某市某个居民新村的所有家庭作为普遍寻找对象,将上海地区所有的宾馆、饭店作为地毯式寻找对象等。

    普遍寻找法有以下的优势:

    1、地毯式的铺开不会遗漏任何有价值的客户;

    2、寻找过程中接触面广、信息量大、各种意见和需求、客户反应都可能收集到,是分析市场的一种方法;

    3、让更多的人了解到自己的企业。

    当然其缺点也是很明显的:

    1、成本高、费时费力;

    2、容易导致客户的抵触情绪。

    因此,如果活动可能会对客户的工作、生活造成不良的干扰,一定要谨慎进行。

    普遍寻找法可以采用业务员亲自上门、邮件发送、电话、与其他促销活动结合进行的方式展开。

    二、广告寻找法

    这种方法的基本步骤是:(1)向目标顾客群发送广告;(2)吸引顾客上门展开业务活动或者接受反馈。例如,通过媒体发送某个减肥器具的广告,介绍其功能、购买方式、地点、代理和经销办法等,然后在目标区域展开活动。

    广告寻找法的优点是:

    1、传播信息速度快、覆盖面广、重复性好;

    2、相对普遍寻找法更加省时省力;

    其缺点是需要支付广告费用、针对性和及时反馈性不强。

    三、介绍寻找法

    这种方法是业务员通过他人的直接介绍或者提供的信息进行顾客寻找,可以通过业务员的熟人、朋友等社会关系,也可以通过企业的合作伙伴、客户等由他们进行介绍,主要方式有电话介绍、口头介绍、信函介绍、名片介绍、口碑效应等。

    利用这个方法的关键是业务员必须注意培养和积累各种关系,为现有客户提供的满意的服务和可能的帮助,并且要虚心地请求他人的帮助。口碑好、业务印象好、乐于助人、与客户关系好、被人信任的业务员一般都能取得有效的突破。

    介绍寻找客户法由于有他人的介绍或者成功案例和依据,成功的可能性非常大,同时也可以降低销售费用,减小成交障碍,因此业务员要重视和珍惜。

    四、资料查阅寻找法

    我们一直认为,业务员要有强的信息处理能力,通过资料查阅寻找客户既能保证一定的可靠性,也减小工作量、提高工作效率,同时也可以最大限度减少业务工作的盲目性和客户的抵触情绪,更重要的是,可以展开先期的客户研究,了解客户的特点、状况,提出适当的客户活动针对性策略等。

    需要注意的是资料的时效性和可靠性,此外,注意对资料(行业的或者客户的)日积月累往往更能有效地展开工作。

    业务员经常利用的资料有:有关政府部门提供的资料、有关行业和协会的资料、国家和地区的统计资料、企业黄页、工商企业目录和产品目录、电视、报纸、杂志、互联网等大众媒体、客户发布的消息、产品介绍、企业内刊等等,而互联网信息虽广而多,但是还是要到一些可信专业的网站上进行相关的资料搜集,比如有关政府部门的资料就是去一些当地的政府部门网站,有关行业和协会的资料就去自己行业协会的网站上去,其他的资料以此类推,找最权威的网站,相关性高,找到的资料才会准确。

    一些有经验的业务员,在出发和客户接触之前,往往会通过大量的资料研究对客户做出非常充分的了解和判断。

    五、委托助手寻找法

    这种方法在国外用得比较多,一般是业务员在自己的业务地区或者客户群中,通过有偿的方式委托特定的人为自己收集信息,了解有关客户和市场、地区的情报资料等等,这优点象香港**使用“线民”,在国内的企业,笔者也见过,就是业务员在企业的中间商中间,委托相关人员定期或者不定期提供一些关于产品、销售的信息。

    另一种方式是,老业务员有时可以委托新业务员从事这方面的工作,对新业务员也是一个有效的锻炼。

    六、客户资料整理法

    这种方法本质上属于“资料查阅寻找法”,但是,也有其特殊性,我们强调客户资料管理,因为其重要性十分突出,现有的客户、与企业联系过的单位、企业举办活动(如公关、市场调查)的参与者等等,他们的信息资料都应该得到良好的处理和保存,这些资料积累到一定的程度,就是一笔财富,在市场营销精耕细作的今天,这尤为重要,这同时也要得力于CRM客户关系管理系统,处理、管理繁杂的客户资料为企业,为个人带来了多大的帮助。现在难以想象如果一个企业没有用到CRM客户关系管理系统或者是OA会变得多么糟糕,如果面对你繁多的客户需要管理还没用到类似工具的话,那你是out了。

    举个最简单的例子,某个家庭,第一代洗衣机购买的是“小天鹅双桶洗衣机”、第二代洗衣机是“小天鹅全自动洗衣机”、第三代洗衣机是“小天鹅滚筒式洗衣机”,你如果要做到真正让客户的三代洗衣机都用“小天鹅”,客户的资料和客户的精细服务就是必不可少的。开个玩笑的话,小天鹅可以提出一个营销内部口号:“让小天鹅在顾客家里代代相传”。

    七、交易会寻找法

    国际国内每年都有不少交易会,如广交会、高交会、中小企业博览会等等,这是一个绝好的商机,要充分利用,交易会不仅实现交易,更重要的是寻找客户、联络感情、沟通了解。奥联的一个客户的老总,参加了今年的广交会回来,向全体员工宣布一个惊人的好消息:“我有足够的信心向大家保证:今年我们的销售收入可以增加2个亿!”,其成效明显主要原因之一是因为其产品的特殊性,但是更重要的是企业已经全面学会和掌握了这个有效的途径。

    八、咨询寻找法

    一些组织,特别是行业组织、技术服务组织、咨询单位等,他们手中往往集中了大量的客户资料和资源以及相关行业和市场信息,通过咨询的方式寻找客户不仅是一个有效的途径,有时还能够获得这些组织的服务、帮助和支持,比如在客户联系、介绍、市场进入方案建议等方面。我们就曾经为一个国外企业进入中国市场提出合作建议方案,寻找合作者甚至参与项目论证等方面提供过一揽子服务。

    九、企业各类活动寻找法

    企业通过公共关系活动、市场调研活动、促销活动、技术支持和售后服务活动等,一般都会直接接触客户,这个过程中对客户的观察、了解、深入的沟通都非常有力,也是一个寻找客户的好方法。

    有效地寻找客户方法远远不止这些,应该说,是一个随时随地的过程。一般信息处理过程是:“所有目标对象-接触和信息处理-初选―精选―重点潜在客户―客户活动计划”。

    展开全文
  • 如何看懂代码--(分析代码方法)

    万次阅读 多人点赞 2018-01-12 16:40:26
    不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的代码解读感到压力。  网路上有一篇关于分析看代码的方法,做为程式设计师的您,不妨参考看看,  换个角度来分析。 也...

    我们在写程式时,有不少时间都是在看别人的代码。 
    例如看小组的代码,看小组整合的守则,若一开始没规划怎么看, 
    就会“噜看噜苦(台语) ” 

    不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力。 
    网路上有一篇关于分析看代码的方法,做为程式设计师的您,不妨参考看看, 
    换个角度来分析。 也能更有效率的解读你想要的程式码片段。 

    六个章节: 
    ( 1 )读懂程式码,使心法皆为我所用。 
    ( 2 )摸清架构,便可轻松掌握全貌。 
    ( 3 )优质工具在手,读懂程式非难事。 
    ( 4 )望文生义,进而推敲组件的作用。 
    ( 5 )找到程式入口,再由上而下抽丝剥茧。 
    ( 6 )阅读的乐趣,透过程式码认识作者。 

     

    阅读他人的程式码( 1 ) ---读懂程式码,使心法皆为我所用 

    程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。但是,与其抗拒接收别人的程式码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。

    对大多数的程式人来说,撰写程式码或许是令人开心的一件事情,但我相信,有更多人视阅读他人所写成的程式码为畏途。许多人宁可自己重新写过一遍程式码,也不愿意接收别人的程式码,进而修正错误,维护它们,甚至加强功能。 

    这其中的关键究竟在何处呢?若是一语道破,其实也很简单,程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。这是来自于人类内心深处对于陌生事物的原始恐惧。 

    读懂别人写的程式码,让你收获满满 
    不过,基于许多现实的原因,程式人时常受迫要去接收别人的程式码。例如,同事离职了,必须接手他遗留下来的工作,也有可能你是刚进部门的菜鸟,而同事经验值够了,升级了,风水轮流转,一代菜鸟换菜鸟。甚至,你的公司所承接的专案,必须接手或是整合客户前一个厂商所遗留下来的系统,你们手上只有那套系统的原始码(运气好时,还有数量不等的文件) 。 

    诸如此类的故事,其实时常在程式人身边或身上持续上演着。许多程式人都将接手他人的程式码,当做一件悲惨的事情。每个人都不想接手别人所撰写的程式码,因为不想花时间去探索,宁可将生产力花在产生新的程式码,而不是耗费在了解这些程式码上。 

    很遗憾的是,上述的情况对程式人来说很难避免。我们总是必须碰触到其他人所写成的程式码,甚至必须了解它,加以修改。对于这项需求,在现今开放原始码的风气如此盛行的今日,正如之前的“程式设计2.0 ”文中所提到的,你可以透过开放原始码学习到新的技术,学习到高手的架构设计,大幅提高学习的效率及效果。你甚至可以直接自开放原始码专案中抽取,提炼出自己所需的程式码,站在巨人的肩膀上,直接由彼端获得所需的生产力。从这个观点来看,读懂别人所写的程式码,就不再只是从负面观点的“被迫接收” ,而是极具正面价值的“汲取养份。 ” 

    先了解系统架构与行为模式,再细读 
    倘若撰写程式码是程式人的重要技艺之一,那么读懂别人的程式码,接着加以修改,也势必是另一个重要的技艺。 

    如果你不能熟悉这项工作,不仅在遭逢你所不愿面对的局面时,无法解决眼前接手他人程式码的难题,更重要的是,当你看着眼前现成的程式码,却不知如何从中撷取自己所需,导致最后只能入宝山空手回,望之兴叹。 

    接触他人的程式码,大致上可以分为三种程度:一,了解,二,修改,扩充,三,抽取,提炼。了解别人的程式码是最基础的工作,倘若不能了解自己要处理的程式码,就甭论修改或扩充,更不可能去芜存菁,从中萃取出自己所需,回收再利用别人所撰写的程式码。虽说是“阅读” ,但程式码并不像文章或小说一样,透过这种做法,便能够获得一定程度的了解。阅读文章或小说时,几乎都是循序地阅读,你只消翻开第一页,一行行阅读下去即可。但是,有许多程式人在试着阅读其他人的程式码时,却往往有不知如何读起的困难。 

    或许找到系统的第一页(也就是程式码执行的启始点)并不难,但是复杂度高的系统,有时十分庞大,有时千头万绪。 

    从程式码的启始点开始读起,一来要循序读完所有的程式码旷日费时,二来透过这种方式来了解系统,很难在脑中构建出系统的面貌,进而了解到系统真正的行为。所以,阅读程式码的重点,不在于读完每一行程式码,而是在于有效率地透过探索及阅读,从而了解系统的架构及行为模式。以便在你需要了解任何片段的细节实作时,能够很快在脑上对映到具体的程式码位置,直到那一刻,才是细读的时机。 

    熟悉沟通语言与惯例用语 
    不论如何,有些基本的准备,是阅读他人程式码时必须要有的。 

    首先,你最好得了解程式码写成的程式语言。想要读懂法文写成的小说,总不能连法文都不懂吧。有些情况则很特殊。我们虽然不懂该程式码撰写所用的语言,但是因为现代语言的高阶化,而且流行的程式语言多半都是血统相近,所以即使不那么熟悉,有时也可勉力为之。 

    除了认识所用语言之外,再来就是要先确认程式码所用的命名惯例(命名惯例) 。了解命名惯例很重要,不同的程式人或开发团队,差异可能很大。 
    这命名惯例涵盖的范围通常包括了变数的名称,函式的名称,类别(如果是物件导向的话)的名称,原始码档案,甚至是专案建构目录的名称。倘若使用了像设计模式之类的方法,这些名称更有一些具体的表述方式。 

    命名惯例有点像是程式人在程式语言之上,另行建构的一组沟通行话。程式人会透过共通约束,遵守的命名惯例,来表达一些较高阶的概念。例如,有名的匈牙利式命名法,便将变数名称以属性,型别,说明合并在一起描述。对程式人来说,这种方式能够提供更丰富的资讯,以了解该变数的作用及性质。 

    对程式码阅读来说,熟悉这个做法之所以重要,是因为当你了解整个系统所采用的惯例时,你便能试着以他们所共同操用的语汇来进行理解。倘若,不能了解其所用的惯例,那么这些额外提供的资讯,就无法为你所用。像以设计模式写成的程式码,同样处处充满着模式的名称,诸如:工厂,门面,代理等等。以这些名称指涉的类别,也直接透过名称,表达了它们自身的作用。对于懂得这命名惯例的读者来说,不需要深入探索,也能很快捕捉到这些类别的意义。 

    当你拿到一套必须阅读的程式码时,最好先取得命名惯例的说明文件。然而,并不是每套程式码都附有此类的说明文件。另一个方式,就是自己到程式码中,大略浏览一遍,有经验的程式人可以轻易发掘出该系统所用的命名惯例。 

    常见的命名方式不脱那几类,这时候经验就很重要,倘若你知道的惯例越多,就越能轻易识别他人所用的惯例。如果运气很糟,程式码所用的惯例是前所未见的,那么你也得花点时间归纳,凭自己的力量找出这程式码命名上的规则。 

    掌握程式码撰写者的心态与习惯 
    大多数的程式码,基本上都依循一致的命名惯例。不过运气更差的时候,一套系统中可能会充斥着多套命名惯例。这有可能是因为开发团队由多组人马所构成,每组人马都有不同的文化,而在专案开发管理又没有管控得宜所造成。最糟的情况,程式码完全没有明显的惯例可言,这时候阅读的难度就更高了。 

    想要阅读程式码,得先试着体会程式码作者的“心” 。想要这么做,就得多了解对方所使用的语言,以及惯常运用的语汇。在下一回中,我们将继续探讨阅读程式码的相关议题。 


     

    阅读他人的程式码( 2 ) -摸清架构,便可轻松掌握全貌

    在本文中,我们的重点放在:要了解一个系统,最好是采取由上至下的方式。先试着捕捉系统架构性的观念,不要过早钻进细节,因为那通常对于你了解全貌,没有多大的帮助。阅读程式码不需要从第一行读起,我们的目的并不是在于读遍每一段程式码。

    基于许多原因,程式人需要阅读其他人所写成的程式码。而对程式设计2.0时代的程式人来说,最正面的价值在于,能读懂别人程式的人,才有能力从中萃取自己所需的程式,借以提高生产力。 

    阅读程式码的目的,在于了解全貌而非细节 
    想要读懂别人程式码的根本基础,便是了解对方所用的程式语言及命名惯例。有了这个基础之后,才算是具备了基本的阅读能力。正如我之前提到的─ ─想要读懂法文写成的小说,总不能连法文都不懂吧。阅读程式码和阅读文学作品,都需要了解撰写所用的语言及作者习用的语汇。 

    但我们在阅读文学作品通常是采循序的方式,也就是从第一页开始,一行一行地读下去,依循作者为你铺陈的步调,逐渐进到他为你准备好的世界里。阅读程式码却大大不同。我们很少从第一行开始读起,因为除非它是很简单的单执行绪程式,否则很少这么做。因为要是这么做,就很难了解整个系统的全貌。是的,我们这边提到了一个重点,阅读程式码的目的在于了解系统的全貌,而不是在于只是为了地毯式的读遍每一段程式码。 

    就拿物件导向程式语言所写成的系统来说,整个系统被拆解,分析成为一个个独立的类别。阅读个别类别的程式码,或许可以明白每项类别物件个别的行为。但对于各类别物件之间如何交互影响,如何协同工作,又很容易陷入盲人摸象的困境。这是因为各类别的程式码,只描述个别物件的行为,而片段的阅读就只能造就片面的认识。 

    由上而下厘清架构后,便可轻易理解组成关系 
    如果你想要跳脱困境,不想浪费大量时间阅读程式码,却始终只能捕捉到对系统片段认识,就必须转换到另一种观点来看待系统。从个别的类别行为着手,是由下至上(自下而上)的方法;在阅读程式码时,却应该先采由上至下(自上而下)的方式。对程式码的阅读来说,由上至下意谓着,你得先了解整个系统架构。 

    系统的架构是整个系统的骨干,支柱。它表现出系统最突出的特征。知道系统架构究竟属于那一种类型,通常大大有益于了解系统的个别组成之间的静态及动态关系。有些系统因为所用的技术或框架的关系,决定了最上层的架构。例如,采用的Java Servlet的/ JSP的技术的应用系统,最外层的架构便是以J2EE的(或起码的J2EE中的Web容器)为根本。 

    使用的Java Servlet的/ JSP的技术时,决定了某些组成之间的关系。例如, Web容器依据web.xml中的内容载入所有的Servlets ,听众,以及过滤器。每当语境发生事件(例如初始化)时,它便会通知监听类别。每当它收到来自客户端的请求时,便会依循设定的所有过滤器链,让每个过滤器都有机会检查并处理此一请求,最后再将请求导至用来处理该请求的Servlet的。 

    当我们明白某个系统采用这样的架构时,便可以很容易地知道各个组成之间的关系。即使我们还不知道究竟有多少Servlets ,但我们会知道,每当收到一个请求时,总是会有个相对应的服务器来处理它。当想要关注某个请求如何处理时,我应该去找出这个请求对应的服务器。 

    了解架构,必须要加上层次感 
    同样的,以爪哇写成的网页应用程式中,也许会应用诸如Struts的之类的的MVC框架,以及像Hibernate的这样的资料存取框架。它们都可以视为最主要的架构下的较次级架构。而各个应用系统,甚至有可能在Struts的及休眠之下,建立自有的更次级的架构。 

    也就是说,当我们谈到“架构”这样的观念时,必须要有层次感。而不论是那一层级的架构,都会定义出各自的角色,以及角色间的关系。对阅读者来说,相较于直接切入最细微的单一角色行为,不如了解某个特定的架构中,究竟存在多少角色,以及这些角色之间的互动模式,比较能够帮助我们了解整个系统的运作方式。 

    这是一个很重要的关键,当你试着进到最细节处之前,应该先试着找出参与的角色,及他们之间的关系。例如,对事件驱动式的架构而言,有3个很重要的角色。一个是事件处理的分派器(事件调度) ,一个是事件产生者(事件发生器) ,另一个则是事件处理器(事件处理程序) 。 

    事件产生器产生事件,并送至事件分派器,而事件分派器负责找出各事件相对应的事件处理器,并且转交该事件,并命令事件处理器加以处理。像的图形用户界面的Windows应用程式,便是采用事件驱动式的架构。 

    当你知道此类的应用程式皆为事件驱动式的架构时,你便可以进一步得知,在这样的架构下会有3种主要的角色。虽然也许还不清楚整个系统中,究竟会需要处理多少事件的类型,但对你而言,已经建立了对系统全貌最概观的认识。 

    虽然你还不清楚所有的细节,但诸如确切会有那些事件类型之类的资讯,在此刻还不重要─ ─不要忘了,我们采取的是由上而下的方式,要先摸清楚主建筑结构,至于壁纸的花色怎么处理,那是到了尾声时才会做的事。 

    探索架构的第一件事:找出系统如何初始化 
    有经验的程式人,对于时常被运用的架构都很熟悉。常常只需要瞧上几眼,就能明白一个系统所用的架构,自然就能够直接联想到其中会存在的角色,以及角色间的关系。然而,并不是每个系统所用的架构,都是大众所熟悉,或是一眼能够望穿的。这时候,你需要探索。目标同样要放在界定其中的角色,以及角色间的静态,动态关系。 

    不论某个系统所采用的架构是否为大部分人所熟知的,在试着探索一个系统的长相时,我们应该找出来几个答案,了解在它所用的架构下,下列这件事是如何被完成的:一,系统如何初始化,二,与这个系统相接的其他系统(或使用者)有那些,而相接的介面又是什么;三,系统如何反应各种事件,四,系统如何处理各种异常及错误。 

    系统如何初始化是很重要的一件事,因为初始化是为了接下来的所有事物而做的准备。从初始化的方式,内容,能知道系统做了什么准备,对于系统会有什么行为展现,也就能得窥一二了。之所以要了解与系统相接的其他系统(或使用者) ,为的是要界定出系统的边界。其他的系统可能会提供输入给我们所探索的系统,也可能接收来自这系统的输出,了解这边界所在,才能确定系统的外观。 

    而系统所反应的事件类型,以及如何反应,基本上就代表着系统本身的主要行为模式。最后,我们必须了解系统处理异常及错误的方式,这同样也是系统的重要行为,但容易被忽略。之前,我们提到必须先具备一个系统的语言基础,才能够进一步加以阅读,而在本文中,我们的重点放在:要了解一个系统,最好是采取由上至下的方式。先试着捕捉系统架构性的观念,不要过早钻进细节,因为那通常对于你了解全貌,没有多大的帮助。

     

     

     

    阅读他人的程式码( 3 ) -优质工具在手,读懂程式非难事

     

     

     

    系统的复杂度往往超过人脑的负荷。阅读程式码的时候,你会需要更多工具提供协助。使用好的整合式开发环境( IDE )的或文字编辑器,就能提供最基本的帮助。

    阅读程式码的动作,可以是很原始的,利用最简单的文字编辑器,逐一开启原始码,然后凭借着一己的组织能力,在不同的程式码间跳跃,拼凑出脑中想要构建的图像。 
    不过,系统的复杂度往往超过人脑的负荷。阅读程式码的时候,你会需要更多工具提供协助。使用好的整合式开发环境( IDE )的或文字编辑器,就能提供最基本的帮助。 

    善用文字编辑器或IDE中,加速解读程式码 
    许多文字编辑器提供了常见程式语言的语法及关键字标示功能。这对于阅读来说,绝对能够起很大的作用。有些文字编辑器(例如我常用的编辑器及偶而使用的记事本+ + ) ,甚至能够自动列出某个原始档中所有定义的函式清单,更允许你直接从清单中选择函式,直接跳跃到该函式的定义位置。这对于阅读程式码的人来说,就提供了极佳的便利性。 

    因为在阅读程式码时,最常做的事,就是随着程式中的某个控制流,将阅读的重心,从某个函式移至它所呼叫的另一个函式。所以对程式人来说,阅读程式码时最常做的事之一就是:找出某个函式位在那一个原始档里,接着找到该函式所在的位置。 

    好的的IDE能够提供的协助就更多了。有些能够自动呈现一些额外的资讯,最有用的莫过于函式的原型宣告了。例如,有些的IDE支援当游标停留在某函式名称上一段时间后,它会以提示的方式显示该函式的原型宣告。 

    对阅读程式码的人来说,在看到程式码中呼叫到某个函式时,可以直接利用这样的支援,立即取得和这个函式有关的原型资讯,马上就能知道呼叫该函式所传入的各个引数的意义,而不必等到将该函式的定义位置找出后,才能明白这件事。 

    grep按(读者:推荐来源透视)是一个基本而极为有用的工具 
    除了选用好的文字编辑器或的IDE之外,还有一个基本,但却极为有用的工具,它就是grep按。熟悉的Unix作业系统的程式人,对grep按这个公用程式多半都不陌生。 grep按最大的用途,在于它允许我们搜寻某个目录(包括递回进入所有子目录)中所有指定档案,是否有符合指定条件(常数字串或正规表示式)档案。 

    倘若有的话,则能帮你指出所在的位置。这在阅读程式码时的作用极大。当我们随着阅读的脚步,遇上了任何一个不认识,但自认为重要的类别,函式,资料结构定义或变数,我们就得找出它究竟位在这茫茫程式码海中的何处,才能将这个图块从未知变为已知。 
    grep按之所以好用,就是在于当我们发现某个未知的事物时,可以轻易地利用它找出这个未知的事物究竟位在何方。此外,虽说grep按是Unix系统的标准公用程式之一,但是像视窗这样子的平台,也有各种类型的grep按程式。对于在视窗环境工作的程式人来说,可以自行选用觉得称手的工具。 

    gtags可建立索引,让搜寻更有效率 
    grep按虽然好用,但是仍然有一些不足之处。第一个缺点在于它并不会为所搜寻的原始码档案索引。每当你搜寻时,它都会逐一地找出所有的档案,并且读取其中的所有内容,过滤出满足指定条件的档案。当专案的原始码数量太大时,就会产生搜寻效率不高的问题。 

    第二个缺点是它只是一个单纯的文字档搜寻工具,本身并不会剖析原始码所对应的语言语法。当我们只想针对“函式”名称进行搜寻时,它有可能将注解中含有该名称的原始码,也一并找了出来。 

    针对grep按的缺点,打算阅读他人程式码的程式人,可以考虑使用像是gtags这样子的工具。 gtags是源代码的GNU全局标记系统,它不只搜寻文字层次,而且因为具备了各种语言的语法剖析器,所以在搜寻时,可以只针对和语言有关的元素,例如类别名称,函式名称等。 

    而且,它能针对原始码的内容进行索引,这意谓一旦建好索引之后,每次搜寻的动作,都毋需重新读取所有原始码的内容并逐一搜寻。只需要以现成的索引结构为基础,即可有效率的寻找关键段落。 

    gtags提供了基于命令列的程式,让你指定原始码所在的目录执行建立索引的动作。它同时也提供程式让你得如同操作grep按一般,针对索引结构进行搜寻及检索。它提供了许多有用的检索方式,例如找出专案中定义某个资料结构的档案及定义所在的行号,或者是找出专案中所有引用某资料结构的档案,以及引用处的行号。 

    这么一来,你就可以轻易地针对阅读程式码时的需求予以检索。相较于grep按所能提供的支援, gtags这样的工具,简直是强大许多。 

    再搭配htags制作的HTML文件,更是如虎添翼 
    还有一个绝对需要一提的工具。这个叫做htags的工具,能够帮你将已制作完成的索引结构,制作成为一组相互参考的的HTML文件。基本上,利用这样的的HTML文件阅读程式码,比起单纯地直接阅读原始码,来得更有结构。原因是阅读程式码时,这样的的HTML文件,已经为你建立起在各个原始码档案片段间跳跃的链结。例如,图一是针对一个有名的开放原始码专案ffmpeg ,由gtags所产生出来的的HTML文件首页的一部分。 

    1 
    htags工具首先为你找出所有定义的Main ( )函式的档案,并且列出所在的函式。找出的Main ( )函式,时常是阅读程式码的第一步,因为主要( )函式是程式的主要入口点,所有的动作皆由此启动,它是一切事物的源头。 
    凭借htags制作的的HTML文件,你可以轻易地点击超连结,直接进到的Main ( )函式所在的程式码片段,如图二。 

    2 

    当我们检视上述原始码时,发现av_register_all ( )是个陌生,无法了解的事物,而想要搞懂它究竟是什么,可以再继续点击这个函式,如图三。这真是太方便了!阅读至此,你会猛然发现, gtags仿佛就是为了阅读程式码而专门量身打造的利器。 

    3

     

     

     

     

    阅读他人的程式码( 4 ) -望文生义,进而推敲组件的作用

     

     

     

    先建立系统的架构性认识,然后透过名称及命名惯例,就可以推测出各组件的作用。例如:当AOL的Winamp尝试着初始化一个插件时,它会呼叫这个结构中的初始化函式,以便让每个插件程式有机会初始化自己。当AOL的Winamp打算结束自己或结束某个插件的执行时,便会呼叫退出函式。

    在阅读程式码的细节之前,我们应先试着捕捉系统的运作情境。在采取由上至下的方式时,系统性的架构是最顶端的层次,而系统的运作情境,则是在它之下的另一个层次。

    好的说明文件难求,拼凑故事的能力很重要 
    有些系统提供良善的说明文件,也许还利用UML的充分描述系统的运作情境。那么对于阅读者来说,从系统的分析及设计文件着手,便是快速了解系统运作情境的一个途径。
    但是,并不是每个软体专案都伴随着良好的系统文件,而许多极具价值的开放原始码专案,也时常不具备此类的文件。对此,阅读者必须尝试自行捕捉,并适度地记录捕捉到的运作情境。 

    我喜欢将系统的运作情境,比拟成系统会上演的故事情节。在阅读细节性质的程式码前,先知道系统究竟会发生那些故事,是必备的基本功课。你可以利用熟悉或者自己发明的表示工具,描述你所找到的情境。甚至可以只利用简单的列表,直接将它们列出。只要能够达到记录的目的,对程式码阅读来说,都能够提供帮助。或者,你也可以利用基于UML中的类别图,合作图,循序图之类的表示方法,做出更详细的描述。 
    当你能够列出系统可能会有的情境,表示你对系统所具备的功能,以及在各种情况下的反应,都具备概括性的认识。以此为基础,便可在任何需要的时候,钻进细节处深入了解。 

    探索架构的第一步─ ─找到程式的入口 
    在之前,我们在一个开发专案中,曾经需要将系统所得到的的MP3音讯档,放至iPod的这个极受欢迎的播放设备中。 

    虽然iPod的本身也可以做为可移动式的储存设备,但并不是单纯地将MP3播放档案放到中的iPod ,就可以让苹果的播放器认得这个档案,甚至能够加以播放。 
    这是因为苹果利用一个特殊的档案结构( iTunes的数据库) ,记录播放器中可供播放的乐曲,播放清单以及乐曲资讯(例如专辑名称,乐曲长度,演唱者等) 。为了了解并且试着重复使用既有的程式码,我们找到了一个AOL的Winamp的iPod的外挂程式(插件) 。 

    AOL的Winamp是个人电脑上极受欢迎的播放软体,而我们找到的外挂程式,能让的软件直接显示连接至电脑的的iPod中的歌曲资讯,并且允许的软件直接播放。 

    我们追踪与阅读这个外挂程式的思路及步骤如下,首先,我们要先了解外挂程式的系统架构。很明显的,大概浏览过原始码后,我们注意到它依循着AOL的Winamp为插件程式所制定的规范,也就是说,它是实作成的Windows上的DLL的,并且透过一个叫做winampGetMediaLibraryPlugin的DLL的函式,提供一个名为winampMediaLibraryPlugin的结构。 
    当我们不清楚系统的架构究竟为何时,我们会试着探索,而第一步,便是找到程式的入口。如何找到呢?这会依程式的性质不同而有所差别。 
    对一个本身就是可独立执行的程式来说,我们会找启动程式的主要函式,例如对的C / C + +来说就是主要( ) ,而对爪哇来说,便是静无效的main ( ) 。在找到入口后,再逐一追踪,摸索出系统的架构。 
    但有时,我们所欲阅读的程式码是类别库或函式库,它只是用来提供多个类别或函式供用户端程式(客户程序)使用,本身并不具单一入口,此类的程式码具有多重的入口─ ─每个允许用户端程式呼叫的函式或类别,都是它可能的入口。 

    例如,对AOL的Winamp的iPod的插件来说,它是一个动态链接库形式的函式库,所以当我们想了解它的架构时,必须要先找出它对外提供的函式,而对的Windows的DLL来说,对外提供的函式,皆会以dllexport这个关键字来修饰。所以,不论是利用grep按或gtags之类的工具,我们可以很快从原始码中,找到它只有一个DLL的函式(这对我们而言,真是一个好消息) ,而这个函式便是上述的winampGetMediaLibraryPlugin 。 

    系统多会采用相同的架构处理插件程式 
    如果经验不够的话,也许无法直接猜出这个函式的作用。 
    不过,如果你是个有经验的程式人,多半能从函式所回传的结构,猜出这个函式实际的用途。而事实上,当你已经知道它是一个插件程式时,就应该要明白,它可能采用的,就是许多系统都采用的相同架构处理插件程式。 

    当一个系统采用所谓插件形式的架构时,它通常不会知道它的插件究竟会怎么实作,实作什么功能。它只会规范插件程式需要满足某个特定介面。当系统初始化时,所有的插件都可以依循相同的方式,向系统注册,合法宣示自己的存在。 

    虽然系统并不确切知道插件会有什么行为展现,但是因为它制定了一个标准的介面,所以系统仍然可以预期每个插件能够处理的动作类型。这些动作具体上怎么执行,对系统来说并不重要。这也正是物件导向程式设计中的“多型”观念。 

    随着实务经验,归纳常见的架构模式 
    我想表达的重点,是当你“涉世越深”之后,所接触的架构越多,就越能触类旁通。只需要瞧上几眼,就能明白系统所用的架构,自然就能够直接联想到其中可能存在的角色,以及角色间的关系。 

    像上述的插件程式手法,时常可以在许多允许“外挂”程式码的系统中看到。所以,有经验的阅读者,多半能够立即反应,知道像这样的系统的软件,应该是让每个插件程式,都写成DLL的函式库。 

    而每个插件的DLL的函式库中,都必须提供winampGetMediaLibraryPlugin ( )这个函式(如果你熟悉的Windows的程式设计,你会知道这是利用加载( )和GetProcAddress ( )来达成的一种多型手法) 。如果你熟悉设计模式,你更会知道这是简单工厂方法这个设计模式的运用。 
    winampGetMediaLibraryPlugin ( )所回传的winampMediaLibraryPlugin结构,正好就描述了每个AOL的Winamp插件的实作内容。 

    善用名称可加速了解 
    利用gtags这个工具,我们立即发现,这个插件它所定义的初始化,退出, PluginMessageProc这三个名称,都是函式名称。这暗示在多型的作用下,它们都是在某些时间点,会由AOL的Winamp核心本体呼叫的函式。 

    名称及命名惯例是很重要的。看到“初始化” ,我们会知道它的作用多半是进行初始化的动作,而“退出”大概就是结束时处理函式,而PluginMessageProc多半就是各种讯息的处理常式(过程通常是程序的简写,所以PluginMessageProc意指插件讯息程序)了。 

    “望文生义”很重要,我们看到函式的名称,就可以猜想到它所代表的作用,例如:当AOL的Winamp尝试着初始化一个插件时,它会呼叫这个结构中的初始化函式,以便让每个插件程式有机会初始化自己;当AOL的Winamp打算结束自己或结束某个插件的执行时,便会呼叫退出函式。当AOL的Winamp要和插件程式沟通时,它会发送各种不同的讯息至插件,而插件程式必须对此做出回应。 

    我们甚至不需要检视这几个函式的内容,就可以做出推测,而这样的假设,事实上也是正确的。

     

     

     

    阅读他人的程式码( 5 ) -找到程式入口,再由上而下抽丝剥茧

     

     

     

    根据需要决定展开的层数,或展开特定节点,并记录树状结构,然后适度忽略不需要了解的细节─这是一个很重要的态度。因为你不会一次就需要所有的细节,阅读都是有目的的,每次的阅读也许都在探索程式中不同的区域。

    探索系统架构的第一步,就是找到程式的入口点。找到入口点后,多半采取由上而下(自上而下)的方式,由最外层的结构,一层一层逐渐探索越来越多的细节。 
    我们的开发团队曾针对AOL的Winamp的iPod的插件进行阅读及探索,不仅找到入口点,也找出,并理解它最根本的基础架构。从这个入口点,可以往下再展开一层,分别找到三个重要的组成及其意义: 
    ●的init ( ) :初始化动作 
         ●退出( ) :终止化动作 
         ● PluginMessageProc ( ) :以讯息的方式处理程式所必须处理的各种事件

    展开的同时,随手记录树状结构 
    当我们从一个入口点找到三个分支后,可以顺着每个分支再展开一层,所以分别继续阅读的init ,退出,以及PluginMessageProc的内容,并试着再展开一层。阅读的同时,你可以在文件中试着记录展开的树状结构。 
    ●的init ( ) :初始化动作 
         ● itunesdb_init_cc ( ) :建立存取iTunes的数据库的同步物件 
         ●初始化资料结构 
         ●初始化的GUI元素 
         ●载入设定 
         ●建立日志档 
         ● autoDetectIpod ( ) :侦测的iPod插入的执行绪 
         ●退出( ) :终止化动作 
         ● itunesdb_del_cc ( ) :终止存取iTunes的数据库的同步物件 
         ●关闭日志档 
         ●终止化图形用户界面元素 
         ● PluginMessageProc ( ) :以讯息的方式处理程式所必须面临的各种事件
         ●执行所连接之苹果的MessageProc ( ) 

    这部分必须要留意几个重点。首先,应该一边阅读,一边记录文件。因为人的记忆力通常有限,对于陌生的事物更是容易遗忘,因此边阅读边记录,是很好的辅助。 
    再者,因为我们采取由上而下的方式,从一个点再分支出去成为多个点,因此,通常也会以树状的方式记录。除此之外,每次只试着往下探索一层。从的init ( )来看你便会明白。 

    以下试着摘要的init ( )的内容: 
    诠释的init ( ) ( 
    itunesdb_init_cc ( ) ; 
    currentiPod =空; 
    苹果=新C_ItemList ; 
    ...略 
    conf_file = (字符* ) SendMessage 
    ( plugin.hwndWinampParent , WM_WA_IPC , 0 , IPC_GETINIFILE ) ; 
    m_treeview = GetDlgItem ( plugin.hwnd LibraryParent , 0x3fd ) ; 
    / /这个数字实际上是魔术: ) 
    ...略 
    g_detectAll = GetPrivateProfileInt ( “ ml_ipod ” , “ detectAll ” , 0 , conf_file ) ! = 0 ; 
    ...略 
    g_log = GetPrivateProfileInt ( “ ml_ipod ” , “日志” , 0 , conf_file ) ! = 0 ; 
    ...略 
    g_logfile =打开( g_logfilepath ,有“ A ” ) ; 
    ...略 
    autoDetectIpod ( ) ; 
    返回0 ; 
    ) 

    因为我们只试着多探索一层,而目的是希望发掘出下一层的子动作。所以在的init ( )中看到像“ itunesdb_init_cc ( ) ; ”这样的函式呼叫动作时,我们知道它是在初始化( )之下的一个独立子动作,所以可以直接将它列入。但是当看到如下的程式行: 
    currentiPod =空; 
    苹果=新C_ItemList ; 

    我们并不会将它视为的init ( )下的一个独立的子动作。因为好几行程式,才构成一个具有独立抽象意义的子动作。例如以上这两行构成了一个独立的抽象意义,也就是初始化所需的资料结构。 

    理论上,原来的程式撰写者,有可能撰写一个叫做init_data_structure ( )的函式,包含这两行程式码。这样做可读性更高,然而基于种种理由,原作者并没有这么做。身为阅读者,必须自行解读,将这几行合并成单一个子动作,并赋予它一个独立的意义─ ─初始化资料结构。 

    无法望文生义的函式,先试着预看一层 
    对于某些不明作用的函式叫用,不是望其文便能生其义的。当我们看到“ itunesdb_init_cc ( ) ”这个名称时,我们或许能从“ itunesdb_init ”的字眼意识到这个函式和苹果所采用的的iTunes数据库的初始化有关,但“循环”却实在令人费解。为了理解这一层某个子动作的真实意义,有时免不了要往前多看一层。 

    原来它是用来初始化同步化机制用的物件。作用在于这程式一定是用了某个内部的资料结构来储存的iTunes数据库,而这资料结构有可能被多执行绪存取,所以必须以同步物件(此处是视窗的临界区)加以保护。 

    所以说,当我们试着以树状的方式,逐一展开每个动作的子动作时,有时必须多看一层,才能真正了解子动作的意义。因为有了这样的动作,我们可以在展开树状结构中,为itunesdb_init_cc ( )附上补充说明:建立存取iTunes的数据库的同步物件。这么一来,当我们在检视自己所写下的树状结构时,就能轻易一目了然的理解每个子动作的真正作用。 

    根据需要了解的粒度,决定展开的层数 
    我们究竟需要展开多少层呢?这个问题和阅读程式码时所需的“粒度(粒度) ”有关。如果我们只是需要概括性的了解,那么也许展开两层或三层,就能够对程式有基础的认识。倘若需要更深入的了解,就会需要展开更多的层次才行。 

    有时候,你并不是一视同仁地针对每个动作,都展开到相同深度的层次。也许,你会基于特殊的需求,专门针对特定的动作展开至深层。例如,我们阅读AOL的Winamp的iPod插件的程式目录,其实是想从中了解究竟应该如何存取的iPod上的iTunes的数据库,使我们能够将MP3播放歌曲或播放清单加至此数据库中,并于的iPod中播放。 

    当我们层层探索与分解之后,找到了parseIpodDb ( ) ,从函式名称判断它是我们想要的。因为它代表的正是剖析iPod的数据库,正是我们此次阅读的重点,也就达成阅读这程式码的目的。 

    我们强调一种不同的做法:在阅读程式码时,多半采取由上而下的方式,而本文建议了一种记录阅读的方式,就是试着记录探索追踪时层层展开的树状结构。你可以视自己需要,了解的深入程度,再决定要展开的层数。你更可以依据特殊的需要,只展开某个特定的节点,以探索特定的细目。 

    适度地忽略不需要了解的细节,是一个很重要的态度,因为你不会一次就需要所有的细节,阅读都是有目的的。每次的阅读也许都在探索程式中不同的区域;而每次探索时,你都可以增补树状结构中的某个子结构。渐渐地,你就会对这个程式更加的了解。

     

     

     

    阅读他人的程式码( 6 ) -阅读的乐趣:透过程式码认识作者

     

     

     

    即便每个人的写作模式多半受到他人的影响,程式人通常还是会融合多种风格,而成为自己独有的特色,如果你知道作者程式设计的偏好,阅读他的程式码就更得心应手。

    阅读程式码时,多半会采取由上而下,抽丝剥茧的方式。透过记录层层展开的树状结构,程式人可以逐步地建立起对系统的架构观,而且可以依照需要的粒度(粒度) ,决定展开的层次及精致程度。 

    建立架构观点的认识是最重要的事情。虽然这一系列的文章前提为“阅读他人的程式码” ,但我们真正想做的工作,并不在于彻底地详读每一行程式码的细节,而是想要透过重点式的程式码“摘读” ,达到对系统所需程度的了解。每个人在阅读程式码的动机不尽相同,需要了解的程度也就有深浅的分别。只有极为少数的情况下,你才会需要细读每一行程式码。 

    阅读程式码是新时代程式人必备的重要技能 
    这一系列的文章至此已近尾声,回顾曾探讨的主题,我们首先研究了阅读程式码的动机。尤其在开放原始码的风气如此之盛的情况下,妥善利用开放原始码所提供的资源,不仅能够更快学习到新的技术,同时在原始码版权合适时,还可以直接利用现成的程式码,大幅地提高开发阶段的生产力。所以,阅读程式码俨然成为了新时代程式人必备的重要技能之一。 
    接着,我们提到了阅读程式码前的必要准备,包括了对程式语言,命名惯例的了解等等。在此之后,我们反覆提起了“由上而下”的阅读方向的重要性。 
    由上而下的阅读方式,是因为我们重视架构更胜于细节。从最外层的架构逐一向内探索,每往内探索一层,我们了解系统的粒度就增加了一个等级。当你识别出系统所用的架构时,便能够轻易了解在这个架构下会有的角色,以及它们之间的动态及静态的关系。如此一来,许多资讯便不言可喻,毋需额外花费力气,便能够快速理解。 

    好的名称能够摘要性地点出实体的作用 
    追踪原始码时,固然可以用本来的方式,利用编辑器开启所需的档案,然后利用编辑器提供的机制阅读,但是倘若能够善用工具,阅读程式码的效率及品质都能大大提升。在本系列文章中,我们介绍了一些工具,或许你还可以在坊间找到其他更有用的工具。 
    我在这一系列的文章中,实际带着大家阅读,追踪了一个名为ml_pod的开放原始码专案。它是一个AOL的Winamp的iPod的外挂程式。在追踪的过程中,我们试着印证这一系列文中所提到的观念及方法。我们采用逐渐开展的树状结构来记录追踪的过程,并借以建立起对系统的概观认识。 

    就原始码的阅读来说,之前的讨论涉及了工具面及技巧面。但还有一些主题不在这两个范畴之内,例如,善用名称赋予你的提示。名称做为隐喻(隐喻)的作用很大,好的名称能够摘要性地点出实体的作用,例如我们看到autoDetectIpod ( ) ,自然而然能够想像它的作用在于自动(自动)侦测(检测)的iPod的存在。 

    我们在展开树状结构时,有时候需要预看一层,有时却不需要这么做,便可得到印证。程式人都会有惯用的名称以及组合名称的方法,倘若能够从名称上理解,便毋需钻进细节,可以省去相当多的时间。例如,当我们看到parseIpodDb ( )时,便可以轻易了解它是剖析(解析)的iPod的资料库( DB )的,因此便不需要立即钻进parseIpodDb ( )中查看底细。 

    尽管如此,能否理解程式人命名的用意,和自身的经验以及是否了解原作者的文化背景,是息息相关的。 

    命名本身就是一种文化产物。不同的程式人文化,就会衍生出不同的命名文化。当你自己的经验丰富,看过及接触过的程式码也多时,对于名称的感受及联想的能力自然会有不同。 

    这种感受和联想的能力,究竟应该如何精进,很难具体描述。就我个人的经验,多观察不同命名体系的差异,并且尝试归纳彼此之间的异同,有助于更快地提升对名称的感受及联想力。 

    转换立场,理解作者的思考方式 
    除了工具及技巧之外, “想要阅读程式码,得先试着阅读写这个程式码的程式人的心。 ”这句话说来十分抽象,或许也令人难以理解。 

    当你在阅读一段程式码时,或许可以试着转换自己的立场,从旁观者的角度转换成为写作者的心态,揣摩原作者的心理及处境。当你试着设身处地站在他的立场,透过他的思考方式来阅读,追踪他所写下的程式码,将会感觉更加流畅。 

    许多软体专案,都不是由单一程式人所独力完成。因此,在这样的专案中,便有可能呈现多种不同的风格。 

    许多专案会由架构师决定主体的架构及运作,有既定实施的命名惯例,及程式设计需要遵守方针。在多人开发的模式下,越是好的软体专案,越看不出某程式码片段究竟是由谁所写下的。 

    不过,有些开放原始码的专案,往往又整合了其他开放原始码的专案。有的时候,也很难求风格的统一,便会出现混杂的情况。好比之前提到的ml_pod专案,因为程式码中混合了不同的来源,而呈现风格不一致的情况。 

    我在阅读非自己所写的程式码时,会观察原作者写作的习惯,借以对应到脑中所记忆的多种写作模型。在阅读的过程中,读完几行程式码,我会试着猜想原作者在写下这段程式码时的心境。他写下这段程式码的用意是什么?为什么他会采取这样的写法?顺着原作者的思考理路阅读,自己的思考才能更贴近对方写作当时的想法。 

    当你短暂化身为原作者时,才能更轻易的理解他所写下的程式码。 
    如果你能知道原作者的背景,程式设计时的偏好,阅读他的程式码,就更能得心应手了。 

    从程式码着手认识作者独有的风格,进而见贤思齐 
    我在阅读别人写下的程式码时,我会试着猜想,原作者究竟是属于那一种“流派”呢?每个人都有自己独特的写作模式,即便每个人的写作模式多半受到他人的影响─ ─不论是书籍的作者,学习过程中的指导者,或一同参与专案的同侪,但每个程式人通常会融合多种风格,而成为自己独有的风格。 

    物件导向的基本教义派,总是会以他心中觉得最优雅的物件导向方式来撰写程式。而阅读惯用,善用设计模式的程式人所写下的程式码时,不难推想出他会在各种常见的应用情境下,套用哪些模式。 

    有些时候,在阅读之初,你并不知道原作者的习性跟喜好,甚至你也不知道他的功力。但是,在阅读之后,你会慢慢地从一个程式人所写下的程式码,开始认识他。 

    你或许会在阅读他人的程式码时,发现令人拍案叫绝的技巧或设计。你也有可能在阅读的同时,发现原作者所留下的缺失或写作时的缺点,而暗自警惕于心。这也算是阅读他人程式码时的一项乐趣。 

    当你从视阅读他人的程式码为畏途,转变成为可以从中获取乐趣的时候,我想,你又进到了另一个境界。

     


     

     摘要:

    (一)阅读他人的程式码( 1 ) ---读懂程式码,使心法皆为我所用 

                 1.先了解系统架构与行为模式,再细读

                      (在你需要了解任何片段的细节实作时,能够很快在脑上对映到具体的程式码位置,直到那一刻,才是细读的时机)

                 2.熟悉沟通语言与惯例用语

                 3.掌握程式码撰写者的心态与习惯

    (二)阅读他人的程式码( 2 ) -摸清架构,便可轻松掌握全貌

                1.阅读程式码的目的,在于了解全貌而非细节

                 2.由上而下厘清架构后,便可轻易理解组成关系 

                3.了解架构,必须要加上层次感

                 4.探索架构的第一件事:找出系统如何初始化

                5.探索架构的第一步─ ─找到程式的入口 (??)

    (三)阅读他人的程式码( 5 ) -找到程式入口,再由上而下抽丝剥茧 

                1.展开的同时,随手记录树状结构 

                2.无法望文生义的函式,先试着预看一层 

                3.根据需要了解的粒度,决定展开的层数 

    展开全文
  • 【Python应用】寻找社交网络中的目标用户 这是我们学校的软件工程课程设计的题目,要求自行编写爬虫或者利用开放的API获取新浪微博、知乎等社交网站的用户信息,利用数据挖掘的相关算法进行分析, 从大规模的用户群体...

    【Python应用】寻找社交网络中的目标用户

    日后的更新:由于是很久以前的课程设计项目,完整的源码已经不见了,关键的网页数据获取和解析的部分代码我在文章中已经贴出来了,但写的也不够好,如果想参考爬取知乎的同学,推荐去看慕课网的《python分布式爬虫打造搜索引擎》,其中有对编写知乎爬虫的视频讲解,虽然已经过去了不短的时间,知乎的反爬策略可能有变,但还是有参考价值的。

    对于爬虫动态代理池的构建,推荐qiyeboy/IPProxyPool这个开源项目,安装的时候可能有点麻烦,见到错误提示缺少哪个包手动pip install就好了。

    以下是正文:

    这是我们学校的软件工程课程设计的题目,要求自行编写爬虫或者利用开放的API获取新浪微博、知乎等社交网站的用户信息,利用数据挖掘的相关算法进行分析, 从大规模的用户群体中, 分别找出其中具有海淘或母婴购物意向的用户。

    在具体实现方面我选择了知乎作为目标的社交网站,通过Python编写数据爬虫与进行数据的分析,通过数据建立用户特征向量,将已知具有母婴购物倾向的用户与未知购物倾向的用户的特征向量进行对比,进而寻找出具有母婴购物倾向的用户。

    ###目录

    编程前简单的分析

    ###选择知乎的原因

    知乎的个人详情页与问题详情页不需要登陆也能访问,免除了模拟登陆的步骤。

    在知乎无论是问题描述还是个人信息内,标签的使用频繁,能给后续的文本处理与分析带来极大地方便。

    不足之处:知乎专业化程度较高,其在生活化气息不如新浪微博等社交网站。

    选定需要获取的信息

    从个人信息页面与问题详情页面分析我们的爬虫应该爬取的信息:

    用户详情界面的基本分析

    问题详情界面的基本分析

    来逐项考虑每项信息对我们项目的意义:

    • 用户的基本信息——昵称、居住地、教育程度等,一般考虑并不会对是否具有母婴购物倾向具有决定性的影响。

    • 用户关注的话题、问题、专栏——关注的话题能最直观的展现用户的喜好、关注点所在,专栏与话题有重叠,关注的问题数量不同用户间可能会差距较大。

    • 用户的回答,专栏——可以反映用户的特长,不能反映用户的兴趣点。

    • 用户的提问——可能能反映用户的某种需求。问题tag能对问题进行有效概括。

    综上,我决定对每个用户提取其关注的话题与所提的问题、问题的tag来建立用户特征向量。

    ##爬虫的编写

    爬虫选择了使用Python编写,主要使用了其中的requests, beautifulsoup等库。

    ###一些难点与解决方法

    爬虫实现过程中遇到的一些阻碍与大体解决方法如下

    • 直接爬虫访问知乎遇到了500错误,结合chrome的开发者工具与fiddle网络分析工具,分析对知乎进行访问的网络请求,在爬虫头部加入了伪造的请求头。

    • 前端知识较为缺乏,对HTML的DOM树结构了解不够深入,对beautifulsoup库不能很好的利用,对一些内容的爬取还需结合正则表达式来操作。

    • 在获取母婴用品话题的关注用户的时候无法绕过登陆过程,而且需要对动态的网页内容进行爬取,此时只能通过selenium+chrome的组合自动填入登陆表单信息与下拉滚动条获取用户id,手动点击验证码的方式完成(现在知乎的登陆验证是点击图中的倒立文字,完全自动化的爬虫想必很难处理)。

    • 对于知乎会限制固定ip访问频率的问题,通过在网上批量购买ip代理地址、建立ip代理池解决,但ip代理池的更新还没能做到自动化。

    • 对于单个爬虫爬取效率太低的问题,由于爬虫完整运行一次的时间主要由网络延迟决定,所以使用python的多线程编程爬虫提高爬取效率。

    • 爬取的数据同步存储到mysql数据库中,使用python的pymysql库对数据库进行操作。

    爬虫的大致结构

    爬虫工作分为三个阶段:第一阶段为随机获取一定数量的用户id,第二阶段为获取一定数量已知具有母婴购物倾向的用户id,第三阶段为根据已经获取的用户id去获取用户的个人信息与提问详情。

    • 第一阶段的工作中,以随机的一位用户作为种子用户,获取其关注的20位用户id,对这20位用户再获取其每个人关注的20位用户,如此循环,预计获取8000位用户作为未知购物倾向的用户组,将用户id写入数据库中。

    • 第二阶段的工作中,前往知乎“母婴用品”话题关注者页面,获取页面下1500名关注者,作为已知具有母婴购物倾向的用户的用户组,同样将用户id写入数据库中。

    • 第三阶段的工作中,通过数据库中的用户id,获取其关注的话题列表与已经提问的问题的标签列表。

    其中第三阶段,也是爬虫主要阶段的主要代码如下:

    # -*- coding:utf-8 -*-
    from bs4 import BeautifulSoup
    import requests
    import re
    import pymysql
    import threading
    import queue
    import time
    
    #用于保存用户uid在数据库中序号的队列
    q = queue.Queue(maxsize=3000)
    
    conn_0 = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root',
                                   db='zhihu', charset='utf8')
    
    cursor_0 = conn_0.cursor()
    #将未读取过的用户入列
    sql_0 = 'SELECT order_number from infant_userid where is_read = 0'
    cursor_0.execute(sql_0)
    conn_0.commit()
    for r in cursor_0:
        r = re.sub("\D", "", str(r))
        print(r)
        q.put(r)
    
    class zhihu():
        def __init__(self):
            # 连接数据库
            self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='', passwd='',
                                   db='zhihu', charset='utf8')
            self.main_url = 'https://www.zhihu.com/people/'
            self.answers_url = '/answers'
            self.topics_url = '/following/topics?page='
            self.asks_url = '/asks?page='
            self.question_url = 'https://www.zhihu.com/question/'
            self.headers = {
                "Host": "www.zhihu.com",
                "Referer": "https://www.zhihu.com/",
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
            }
    
        def get_userinfo(self, user_token, proxies):
            # 获取用户名字
            url1 = self.main_url + user_token
            content = requests.get(url1, headers=self.headers, proxies=proxies).content
            #print(content.text)
            pattern = re.compile(r'<span class="ProfileHeader-name">(.+?)</span>')
            try:
                name_temp = pattern.findall(content.text)
            except:
                #除去僵尸用户,即未登陆无法查看信息的用户
                sql1 = "delete from infant_userid where token_id = '"+user_token+"'"
                self.cursor.execute(sql1)
                self.conn.commit()
                print(user_token+" is a ghost!!")
                pass
            else:
                print(name_temp)
                name = name_temp[0]
                print(name)
                # 获取用户关注的话题
                url2 = self.main_url + user_token + self.topics_url
                # print("fetching userinfo...")
                n = 1  # 话题的页数
                count = 21
                all_topics = []  # 用于存放话题的列表
                while count > 20:
                    content = requests.get(url2 + str(n), headers=self.headers, proxies=proxies)
                    pattern = re.compile(r'&quot;name&quot;:&quot;(.+?)&quot;')
                    topics = pattern.findall(content.text)
                    all_topics = all_topics + topics
                    n = n + 1
                    count = len(topics)
                all_topics = list(set(all_topics))  # 去重
                topics_str = ";".join(all_topics)  # 转字符串
                print(topics_str)
    
                # 链接数据库
                sql2 = "INSERT INTO infant_users (tokenID,username,topics) VALUES ('" + user_token + "','" + name + "','" + topics_str + "')"
                self.cursor.execute(sql2)
                # print(sql)
                self.get_question(user_token, proxies)
    
        def get_question(self, user_token, proxies):
                # 获取用户的提问
                url = self.main_url + user_token + self.asks_url
                #print(url)
                #print("fetching questions...")
                n = 1  # page
                question_count = 20
                while question_count > 19:
                    content = requests.get(url + str(n), headers=self.headers, proxies=proxies)
                    pattern = re.compile(r'&quot;http://www.zhihu.com/api/v4/questions/(.+?)&quot;')
                    question_tokens = pattern.findall(content.text)
                    # print(question_tokens)
                    for question_token in question_tokens:
                        #print(question_token)
                        url2 = self.question_url + question_token
                        content2 = requests.get(url2, headers=self.headers, proxies=proxies).content
                        soup = BeautifulSoup(content2, 'lxml')
                        try:
                            title = soup.find('h1', attrs={'class', 'QuestionHeader-title'})
                        except:
                            #问题未登陆无法访问的情况
                            print('question is empty')
                            pass
                        else:
                            title = title.get_text()
                            print(title)
                            sql_tags = []
                            tags = soup.find_all('span', attrs={'class', 'Tag-content'})
                            for tag in tags:
                                tag = tag.get_text()
                                sql_tags.append(tag)
                            # print(sql_tags)
                            tags_str = ";".join(sql_tags)  # 转字符串
    
                            # 操作mysql
                            sql1 = "INSERT INTO infant_questions (questionsID,question,tags,askerID) VALUES ('" + question_token + "','" + title + "','" + tags_str + "','" + user_token + "')"
                            # print(sql1)
                            self.cursor.execute(sql1)
                            # time.sleep(0.5)
    
                    # 页面计数加一
                    n = n + 1
                    question_count = len(question_tokens)
                    # print(count)
                #print("fetch questions done")
    
        def run(self):
            count = 0
            while count < 100:
                if not (q.empty()):
                    user_num = q.get()
                    print(user_num)
                    self.cursor = self.conn.cursor()
                    sql = "SELECT token_id FROM infant_userid WHERE order_number='" + str(user_num) + "'"
                    self.cursor.execute(sql)
                    self.conn.commit()
                    try:
                        user_token = self.cursor.fetchone()[0]
                    except:
                        print("获取用户名出错,重试中...")
                        pass
                    else:
                        print("feching the no." + str(user_num) + " user: " + user_token + "...")
                        # 代理,还需手动填入
                        proxies_pool = [{'https': 'https://210.43.38.251:8998'}, {'https': 'https://111.155.116.247:8123'}]
                        proxies_num = 1
                        proxies = []
                        #try:
                        self.get_userinfo(user_token, proxies)
                        #except:
                            #print("发生错误,重试中...")
                            #q.put(user_num)
                            #pass
                        #else:
                        sql2 = "UPDATE infant_userid SET is_read=1 where token_id='" + str(user_token) + "'"
                        self.cursor.execute(sql2)
                        self.conn.commit()
                        count += 1
                        print(count)
                    print("fetch done")
    
            self.cursor.close()
    
    if __name__ == "__main__":
        #定时重启的主线程
        for i in range(1, 10):
            #线程列表
            thread = []
            for i in range(0, 1):
                z = zhihu()
                t = threading.Thread(target=z.run, args=())
                #设为守护进程
                t.setDaemon(True)
                thread.append(t)
            for i in range(0, 1):
                thread[i].start()
            for i in range(0, 1):
                thread[i].join()
            time.sleep(1200)
    

    程序运行时间过长爬取的效率会下降,将主线程设置为定时重启也不行,不知道是哪里出了问题。…

    最终爬取的数据在数据库中预览的效果如下:

    问题详情

    用户信息

    数据库与表的结构就不贴出来了,并不是什么复杂的结构。

    ##数据的处理

    ###初步处理

    • 筛选:剔除无法访问的用户后,得到7792位未知购物倾向用户信息,1209位已知具有母婴购物倾向用户信息。母婴用户下有1141条问题记录,未知倾向用户下有16924条问题记录。

    • 分词:对每个用户关注的话题、问题使用结巴分词进行分词处理。分词过程中整合网络上的停用词表,构建更为全面的停用词表。

    • 生成待分析文本:将每个用户分词后信息导出到一个txt文件,以用户的id作为文件名。未知与已知用户分别置于两个不同文件夹内。

    这样每个用户就有了一个描述该用户的txt文件,下一步将基于这个txt文件的内容计算用户的特征向量。

    ###进一步处理

    • 首先计算已知母婴购物倾向用户的特征向量:计算每个词的卡方统计量,挑选出其中CHI>0.1的词语作为特征向量,然后计算每个词语的TF-IDF值作为特征向量的权重,其乘积便为带权重的特征向量。
      CHI值的计算公式:
      CHI值的计算公式
      TF-IDF值的计算公式:
      TF-IDF值的计算公式

    • 建立未知购物倾向用户词表,计算每个词的特征向量值后写入数据库,对每个未知购物倾向用户的分词后文档进行处理,从数据库中查询每个词的特征向量值构成该用户的特征向量。

    • 计算已知具有母婴购物倾向的用户与未知用户的特征向量夹角余弦值,若计算结果大于0.5则认为该未知用户同样具有母婴购物倾向。
      夹角余弦值计算公式

    ##结果

    ###程序运行的结果

    程序运行情况如图:
    程序运行结果

    此时程序运行了25分钟,处理了500+位用户的数据,约占总数的6%,从搜索结果栏可以发现,程序已经寻找出了三位潜在的具有母婴购物倾向的用户。

    如果运行的时间足够长的话,可以从7792位用户中把所有具有母婴购物倾向的用户寻找出来(懒啊…)。

    ###小小的总结
    当然,从运行结果就能看出缺点:程序太慢,效率太低了…建立未知购物倾向用户的特征向量与余弦夹角的计算都需要很多时间。实际上以上分析的方法都只是简单的对包含用户信息的文本进行相似度的比较,其中“计算结果大于0.5则认为该未知用户同样具有母婴购物倾向”也没有任何根据,完全只是为了方便而私人划定的标准,对于获得的“潜在用户”也没有任何校验准确性的方法,整套分析的方法可以说是漏洞百出(这点在课程设计答辩的时候已经被老师指出了),所以这篇文章并不具备任何作为实际参考的意义,仅仅是作为个人python程序练手项目的一次总结与记录。

    这里写图片描述
    期间也想要放弃,虽然掉入了一个又一个的坑,但还是勉勉强强爬了出来…

    在实际编程的工作中遇到的困难比文中提及的要多,例如数据库操作上的困难(去重)、代理设置上的疑问(http与https只差)、手贱删掉已经爬好的用户数据等…虽然其中大多数是低级错误,但一脚一个坑也算是能让我长点记性吧…

    ###参考资料

    展开全文
  • 关于CDN以及如何绕过CDN寻找真实ip

    千次阅读 2021-05-03 22:55:34
    要知道有很多客户使用CDN的一个很大的目的就是为了隐藏自己的ip,所以想查找真实ip也不是那么容易。网上有很多方法,但基本都是千篇一律,放在过去估计行,但是现在压根就没用,都是互相抄来抄去,估计自己都没验证...
  • XILINX FPGA电源设计指南

    万次阅读 多人点赞 2018-08-08 19:54:03
    为 FPGA 应用设计优秀电源管理解决方案不是一项简单的任务,相关的技术讨论有很多很多。...寻找为 FPGA 供电的最佳解决方案并不简单。许多供应商以适合为 FPGA 供电的名义推销某些产品。为 FPGA ...
  • java项目——CRM客户管理系统(SpringBoot+MyBatis)

    万次阅读 多人点赞 2021-06-01 20:23:31
    目录CRM客户管理系统一.对CRM的项目的简单描述1.什么是CRM2.CRM开发环境和技术<1> 项目业务介绍<2>开发环境<3>开发技术二.项目准备及模块分析1.模块分析总览2.项目前期环境的搭建三.项目的正式...
  • 第六章 互联网方法论 附录 周鸿祎批注“遗失的乔布斯访谈” 我的总结 老周的互联网方法论 思维导图 周鸿祎这个人比较有争议,如果不是他,中国互联网的免费文化可能还不会像今天这样,免费,共享等等概念满天...
  • 基于WEB的客户关系管理系统

    千次阅读 2017-11-20 14:29:26
    第一章 客户关系管理绪论 客户关系管理CRM(Customer Relationship Management)最早由美国artner Group提出,自1997年开始,经过几年的发展,全球的CRM市场一直处于爆炸性的快速增长之中。1999年全球的CRM市场...
  • SAP寻找增强点的方法

    千次阅读 2014-04-25 14:52:36
    SAP中寻找增强的实现方法 SAP 增强已经发展过几代了,可参考 SAP 标准教材 BC425 和 BC427。简单的说SAP的用户出口总共有四 代:  1、第一代 基于代码的增强。 SAP提供一个空代码的子过程,在这个子过程中用户...
  • 1.查看方法 打开f12刷新页面找一个 js 或 css 请求查看 Response Headers -> last-modified 如图 2.文章背景 小罗,给看下五屏(5个系统的大屏集中展示)为什么显示白板了?,单独打开没问题,嵌在5屏里面就不行...
  • SAP 寻找增强点的方法

    千次阅读 2014-02-24 11:10:57
    SAP中寻找增强的实现方法 SAP 增强已经发展过几代了,可参考 SAP 标准教材 BC425 和 BC427。简单的说SAP的用户出口总共有四 代:  1、第一代 基于代码的增强。 SAP提供一个空代码的子过程,在这个子过程中用户...
  • 软件工程(三)—— 结构化方法

    千次阅读 2019-03-09 14:37:18
    一、结构化需求分析 在软件系统的需求工作中,通常面临三大挑战,即问题空间理解、人与人之间的通信、需求的变化...③提供定义系统边界的方法; ④提供支持抽象的基本机制; ⑤为需求分析人员提供多种可供选择的...
  • 1. 了解信息收集阶段的目的; 2. 掌握信息收集的类型; 3. 掌握不同信息收集的方法
  • 2)定位于哪个网站上搜索 使用site,如在百度或google中键入“大数据空格site:sina.com”,则在sina.com搜索有关大数据的一些资料信息,这个特别适用针对某些信息可能在哪些网站上出现的一个快速搜索方法,注意冒号...
  • 基于JavaScript+mysql+HTML+CSS的SSM客户管理系统设计和实现【建议收藏】????️‍????目录????1、前言????1.1、需求分析????1.1.1、功能性需求分析????1.1.2、非功能性需求????1.1.3、编写目的????1.1.4项目背景????...
  • SPRING动态数据使用方法

    千次阅读 2013-09-21 17:39:15
    因项目需要,需要使用动态数据,什么叫动态数据,就是数据是由客户动态选择的,不仅仅有一个。这就意味在后台会配置多个数据。   我们的系统有很多版本,不同版本开发在不同的数据库上,但是系统需要的...
  • Java常见设计模式总结

    万次阅读 多人点赞 2021-09-18 17:18:54
    装饰器模式的UML结构图如下: 但是装饰器模式也存在缺点,首先会产生很多的小对象,增加了系统的复杂性,第二是排错比较困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。 装饰器模式详情:...
  • JSP页面之间传值的方法总结

    万次阅读 多人点赞 2017-11-16 16:34:57
    B/S页面间通信 ...Web页面本身无法向下一个页面传递信息,如果需要让下一个页面得知该页面中的值,除非通过服务器。因此,Web页面保持状态并传递给其它页面,是一个重要的技术。 Web页面之间传递数据,...3)Cookie方法
  • 之前寻找直播,发现好多rtmp开头的,或者是rtsp开头的,但是ATV里面的个人链接是支持m3u8格式的。怎么办?小编发现了几个规律,网友可作参考。现在流行的直播地址差不多就这几种需要说明的是并不是所有的地址改成...
  • 应用数据挖掘进行客户关系管理

    千次阅读 2018-05-18 21:16:05
    客户关系管理的目标是缩减销售周期和销售成本、增加收人、寻找扩展业务所需的新的市场和渠道、以及提高客户的价值、满意度、赢利性和忠实度。企业实施客户关系管理,可以更低成本、更高效率地满足客户的需求,从而...
  • 基于人工智能的交易平台在趋势建模方面很受欢迎,它们试图利用大数据寻找被低估的股票。 这就是为什么它很适合成为初学者的第一个人工智能项目。你会喜欢股票市场,因为它充满了信息。你可以获得各种类型的数据集,...
  • 软件工程 实践者的研究方法 中文题答案

    万次阅读 多人点赞 2021-01-27 23:26:33
    19.1用自己的话,描述为什么在面向对象系统中,类是最小的合理测试单元。 答:在面向对象软件中,单元的概念发生...19.8运用随机测试、划分方法、多类测试及19.5, 19.6节所描述的银行应用的行为模型导 出的测试,在另
  • resource定位 即寻找用户定义的bean资源,由 ResourceLoader通过统一的接口Resource接口来完成 beanDefinition载入 BeanDefinitionReader读取、解析Resource定位的资源 成BeanDefinition 载入到ioc中(通过HashMap...
  • spss分析方法-聚类分析

    万次阅读 2022-06-14 16:42:02
    在不同的应用领域,很多聚类技术都得到了发展,这些技术方法被用作描述数据,衡量不同数据间的相似性,以及把数据分类到不同的簇中。商业上:聚类分析被用来发现不同的客户群,并且通过购买模式刻画不同的客户群...
  • 最常见的攻击者用于寻找打开anonymous的FTP服务器的方法。这些服务器带有可读写的目录。木马Doly Trojan、Fore、Invisible FTP、WebEx、WinCrash和Blade Runner所开放的端口。 端口:22 服务:Ssh 说明:PcAnywhere...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,390
精华内容 21,756
热门标签
关键字:

寻找客户源的方法