硕鼠的博客站

范路的博客主站,时而会发些东西。

Archive for 3 月, 2007

 

Delphi for PHP的发表会已经进行了三场,由于使用的是BDN Borland Developer Network)用户作为邀请对象,所以到场的PHP 程序员并不多。但是反响好的出乎我们意料之外。很多到场的用户都表示可以马上购买,也有一些公司和单位在积极的询问价格,有一个公司甚至打电话来说我们希望可以购买这个软件即使比Delphi更贵也是可以接受。这让我感到异常欣慰。

虽然我现在不能披露价格,但是我想应该不会超过Delphi企业版价格的四分之一的。我们终于有了一款可以让开发者公司和个人在价格上能够接受的开发工具了。(关于价格纯属个人猜测,于 CodeGear公司无关,最终定价以CodeGear公司中国代表处发布的官方价格为准)

 

但是我在这里要说的并不是这些,我要说的是我们中国的软件开发行业终于有了一个参与国际软件开发大环境的捷径。现在这几年,国内的软件市场和软件开发行业都有了长足的进步,但是发展也带来了一些问题,首先国内软件市场的竞争不够透明,供求关系不平衡(并不是说供大于求,而是说供求双方不能进行平等的合作和沟通,不是店大欺客就是客大期店),各个行业之间壁垒分明,商业运作的过程中非商业手段的参与占比重非常大。很多希望运用纯商业手段或者至少是不过分依赖非商业手段生存的个人和企业,以及一些已经发展到在国内依靠非商业手段已经无法取得进一步发展的企业(主要是指本行业内没有发展前景或者是已经饱和,其它更有利润的行业有无法进入),开始把目光放在国外。

为国外提供软件服务,目前来说主要是三种方式:劳务输出、软件外包和开发小工具独立销售。让我们来看以下这三种方式的利弊:

劳务输出:适用于个人和各种规模的企业,对于个人的要求就是必须能够熟练的进行外语对话,对于企业而言一般都是进行一些中介的服务,所以必须要有足够的海外关系,比如和一些国外的大公司有很好的合作关系等等,很多这种企业的创始人都是早期被输出出去的个人。这个行业对于企业来说主要的发展方向就是逐渐转变成外包形企业,对于个人来说,主要的发展方向有两个:回国开中介公司;留在国外,在不同企业中跳槽,并且作到比较高的职位。这种方式对于个人来说不失为一种快捷的方式,而且对于个人素质的要求并不是特别高,但是问题是工作强度非常的大,而且报酬里面有很大的一块要被中介公司拿着。

软件外包:只有比较大型的企业才有可能从事,必须要在国内有相当大的规模,同时也要有很高的国外人脉,基本上不适合个人和小团队、小公司涉足。目前我们的外包层次很低,比较高等级的工作都在国外完成了,比如分析、设计、建模等,国内一般从事的都是软件蓝领的工作,比如编码、测试等等,所以这是一个门槛很高,回报率完全依赖与压榨程序员的行业,小公司、小团队和个人完全无法进入,就我个人来看,有良知的大企业也不应该进入(不过大多数企业老总认为帮助外国人压榨中国程序员的剩余价值并不是什么有愧良知的事情,他们大多认为,如果不是他赏了一口饭给这些程序员吃,这些程序员就会俄死街头),对于软件外包公司中供职的程序员来说,境遇是非常悲惨的,即需要有很好的外语基础,也需要有非常高的软件素养,而且工作强度非常大,同时报酬少的可怜,各种社会保障也非常的不完善,个软件输出相比较,简直就是一个天上、一个地下(纯属个人观点,请不要太认真)。这个行业对于企业来说,发展的前景主要在于今后努力的承接更大的项目,明切逐步的能够完成完整的项目,即所有的工作都自己来作。对于个人来说,这绝对是一个没有任何发展前途的行业。

最后说说开发小工具然后独立销售,这种方式非常适合于小形团队、中小型公司和个人来从事,门槛低、回报稳定、发展前景广阔(从不太严格的约束来看,很多目前大型国际知名软件企业就是这么发展起来的,比如微软)。前面所说的小工具,主要指的是可以完成一些独立功能的小型工具类软件,比如某些格式转换器、网络工具、编程相关工具、编程控件、编程框架等等。从事这个行业,基本上没有太高的外语要求,只有团队中有一个能够进行简单英文读写的人,能够维护一个不太复杂的英文网站就可以开展各项相关的业务,不需要任何客户关系, GoogleYahoo会替你将客户送上门来,主要的客户交流都是通过 email(主要是英文)来实现,我们大部分的程序员看英文文档和帮助都没有什么问题,所以从事这项工作应该也问题不大。另外,完全不用为你的英文写得不够标准而烦恼,你的客户,那些日本人、韩国人或者是欧洲人,他们的英文也是非常差的。这个行业对于程序员的业务素质要求还是很高的,毕竟你写的小工具在国外要得到人们的认可,那么质量和创新性是必不可少的,不过不用担心,术也有专攻,程序员只要在某一个特定的领域有这比较深厚的功底就完全可以胜任,毕竟他们的产品只是小工具和小软件,而不是完整的应用系统。这个行业发回报是相当稳定的,即如果国外的团队一旦用上了你的工具或者是组建,那么他们习惯一直用下去,即不断的购买最新的版本和各种支持服务。另外要说的是这个行业规模比较小的时候好像还不用上税,完全可以有个人和小团队在没有进行任何工商注册和登记的情况下进行运作,只要有一张双币种信用卡就可以完成全部的财务操作。这个行业的发展前景非常的好,当然前提是要有好的创意,完全可以向大型开源团队发展,团队很有可能会被国外大公司收购、个人也可能会直接被国外的大公司招揽。另外不一定要作为主业来从事,完全可以一边为资本家打工,一边偷偷的进行。

进入这个行业基本上不需要什么资金支持,只要能够注册一个域名,租用一个空间,建立网站,然后开发人员能够有自己的电脑和相应的开发工具就可以了,当然开发工具最好买正版的,比如说购买了Delphi for PHP ,成为CodeGear正式的合作伙伴,那么以后Delphi for PHP 版本升级的时候,你的产品就有可能进入产品包装中的合作伙伴光盘之中,如果作的确实不错,CodeGear也可能将其购买下来作为产品的一部分进行发售,即使是将你们的整个团队收购下来也不是不可能的事情,但是这一切的前提条件就是,你们是 CodeGear的合作伙伴,这对于大多数团队来说,我想并不困难,毕竟Delphi for PHP 的价格应该比一台台式电脑还要便宜一些。

综上所述,从事小工具开发和独立销售是一个非常有前景的行业,所以我们下面主要围绕这个方向来说,而且我们新产品的推出对于这个行业将具有非常大的推进作用。从事这个行业、首先要记住的就是前期的产品千万不能太大,而且一定要越专业化,越有特色越好。我记得上中学的时候学过�D�D社会分工的不断完善,是生产力进步的标志。国外的软件行业生产力水平比我们高很多,所以他们的分工非常细致,而且整个市场的容量非常巨大,不要担心你的小工具或者是小组件功能过于单一、所针对的用户群过于狭窄,即使在狭窄的用户群他们的购买力也是非常惊人的。当然,在你有了一个跟定的用户群之后,可以将产品的功能不断的完善。

 

小工具开发这个行业有一个非常有趣的规律,那么就是当一个新的领域被打开之后,就会有很多的机会。而且不需要太负杂的创意,不需要很大的工作量,就可以在这个新的领域中圈下一块很大的蛋糕,随着时间的推移,这个新的领域被开发的越来越成熟,再想在里面分一杯羹就越来越难了。而且早期进入的那些人总是能够得到更多的回报,这一点上有点儿像传销。举个例子, Delphi刚刚发布的时候,有很多人就开始为Delphi VCL组建。这些人的组件很早就被大家所接受,随着Delphi的发展,这些人的组件一直被我们广泛的应用着(我们可能没有付费,但是国外的用户有很大比例付费了),而且由于我们已经习惯了这些组件的使用方式,所以即使有能够达到同样功能的组件做得更好一些,我们也会习惯性的继续使用这些老的组件。另外还有很多人会在这些组件的基础上再开发其它的组件,这些人就有些像传销里面的下线客户了。

 

目前就有一个这样的机会,Delphi for PHP的发布,使得一个新的控件开发时代到来了,一个完全的新领域等着我们去开拓。 VCL for PHP虽然目前已经有了50多个控件,能够满足我们的编制简单网站的基本要求,但是很显然这是远远不够的。 Delphi就几百个控件,但是也还是不够的,Delphi for PHP在这方面可以发展的空间肯定是会更大。

 

Delphi诞生的时候,我们国内的软件人员还不多,软件开发水平也比国外要差一些,所以我们没有赶上 Delphi VCL这个大潮,目前赶上这个大潮的那些国外企业有很多已经发展的非常不错了,甚至其中有一些企业的规模比Borland 还要大。现在Delphi for PHP的这个大潮,我们一定不能再错过了。而且现在下手的话,还能够趁着门槛比较低的时候,只付出比较小的代价就圈住一块比较大的蛋糕。

下面让我们来分析以下VCL for PHP的基本结构,李维在作 Demo的时候最喜欢拿出来炫的就是Google Map VCL,你们能够想像得出来这个组件一共有多少行代码吗?

不到100行,是不是吓到了?你有可能编写很少的代码,就可以开发一共非常漂亮的组件,并且开始你自己的软件创业之路。

下面让我们来看一下这个组件的源代码:

<?php

        //Includes

        require_once("vcl/vcl.inc.php");

 

        use_unit("controls.inc.php");

 

        //Class definition

        class GoogleMap extends Control

        {

            function __construct($aowner = null)

            {

                parent::__construct($aowner);

            }

 

            private $_mapskey="ABQIAAAAQlQ8ZvigZnDc1z7MTEuUQxTJO8fVsnY3pyCJC531oZiosu_8phSnTlxi08R1_58Gfdyd9NUJdyES5w";

 

            function getMapsKey() { return $this->_mapskey; }

            function setMapsKey($value) { $this->_mapskey=$value; }

            function defaultMapsKey() { return "ABQIAAAAQlQ8ZvigZnDc1z7MTEuUQxTJO8fVsnY3pyCJC531oZiosu_8phSnTlxi08R1_58Gfdyd9NUJdyES5w"; }

 

            private $_address="Scotts Valley, CA";

 

            function getAddress() { return $this->_address; }

            function setAddress($value) { $this->_address=$value; }

            function defaultAddress() { return "Scotts Valley, CA"; }

 

 

 

            function dumpHeaderCode()

            {

?>

    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key= <?php echo $this->MapsKey; ?>"

      type="text/javascript"></script>

    <script type="text/javascript">

    //<![CDATA[

    function <?php echo $this->Name; ?>load()

    {

      if (GBrowserIsCompatible())

      {

        var map = new GMap2(document.getElementById("<?php echo $this->Name; ?>_div"));

        map.addControl(new GLargeMapControl());

        map.addControl(new GScaleControl());

        map.addControl(new GMapTypeControl());

 

        var geocoder = new GClientGeocoder();

 

        var address="<?php echo $this->Address; ?>";

 

        geocoder.getLatLng (address,

 

        function(point)

        {

                if (!point)

                {

                        alert(address + " not found");

                }

                else

                {

                        map.setCenter(point, 13);

                        var marker = new GMarker(point);

                        map.addOverlay(marker);

                        marker.openInfoWindowHtml(address);

                }

        }

        );

      }

   }

    //]]>

    </script>

<?php

            }

 

            function dumpContents()

            {

                echo "<div id="".$this->Name."_div" style="width: ".$this->Width."px; height: ".$this->Height."px"></div>";

?>

    <script type="text/javascript">

    <?php echo $this->Name; ?>load();

    </script>

<?php

            }

        }

 

?>

 

连空行都算上一共84行。我就不讲解这段代码了,我想不管你是什么语言的程序员, DelphiPHPJava C++C#JavaScript等等等等,你应该都能够读懂上面的几十行代码。

看看,这是多么容易的一件事啊,你也能的。

 

如果你希望能够进入这个行业,那么我给大家指几个方向,供大家参考。在早期,这个领域还是一片空白的时候,大家可以试着从这些方向下手。

第一、    使用现有组件进行一些扩展,完成一些更加具体的功能:

下面这个例子是LabeledEdit组件,将一个 Label和一个Edit组合起来,完成一个完整的功能。

class SubLabel extends Persistent

{

        protected $_caption = "";

 

        function assignTo($dest)

        {

                $dest->_caption=$this->_caption;

        }

 

        /**

         * Specifies the caption used in the label

         *

         * @return string

         */

        protected function readCaption()           { return $this->_caption; }

        protected function writeCaption($value)    { $this->_caption=$value; }

        function defaultCaption()                  { return ""; }

 

        // publish properties

        function getCaption()           { return $this->readCaption(); }

        function setCaption($value)     { $this->writeCaption($value); }

}

 

class CustomLabeledEdit extends CustomTextField

{

        protected $_lblname = "";

 

        protected $_edtlabel=null;

        protected $_lblspacing = 3;

        protected $_lblposition = lpAbove;

        protected $_text = "";

 

        protected function CalculateEditorRect()

        {

                switch ($this->_lblposition)

                {

                  case lpBelow:

                        $y = 0;

                        break;

                  default: // lpAbove:

                        $y = 14 + $this->_lblspacing;

                        break;

                }

                return array(0, $y, $this->Width, $this->Height – 14 – $this->_lblspacing);

        }

 

        protected function dumpExtraControlCode()

        {

                $eh = $this->Height – 14 – $this->_lblspacing;

                switch ($this->_lblposition)

                {

                  case lpBelow:

                        $y = $eh;

                        break;

                  default: // lpAbove:

                        $y = 0;

                        break;

                }

 

                $this->_lblname = $this->Name . "_Lbl";

 

                echo "  var $this->_lblname = new qx.ui.basic.Atom("" . $this->_edtlabel->Caption . "");n"

                   . "  $this->_lblname.setLeft(0);n"

                   . "  $this->_lblname.setTop($y);n"

                   . "  $this->_lblname.setWidth($this->Width);n"

                   . "  $this->_lblname.setHorizontalChildrenAlign("left");n";

 

                if (($this->Visible) || (($this->ControlState & csDesigning)==csDesigning))

                      { $visible="true"; }

                else  { $visible="false"; };

                echo "  $this->_lblname.setVisibility($visible);n"

                   . "  inline_div.add($this->_lblname);n";

        }

 

        function __construct($aowner = null)

        {

                //Calls inherited constructor

                parent::__construct($aowner);

 

                $this->_edtlabel = new SubLabel();

                $this->Width = 121;

                $this->Height = 34;

        }

 

        function setName($value)

        {

                $oldname=$this->_name;

                parent::setName($value);

 

                //Sets the caption if not already changed

                if ($this->_edtlabel->Caption == $oldname)

                {

                        $this->_edtlabel->Caption = $this->Name;

                }

        }

 

        /**

         * Use EditLabel to work with the label that is associated with this

         * labeled edit control. Use this label�s properties to specify the

         * caption that appears on the label.

         */

        protected function readEditLabel()              { return $this->_edtlabel; }

        protected function writeEditLabel($value)       { if (is_object($value)) $this->_edtlabel=$value; }

        /**

         * Specifies the position of the label relative to the edit control.

         *

         * @return enum (lpAbove, lpBelow)

         */

        protected function readLabelPosition()          { return $this->_lblposition; }

        protected function writeLabelPosition($value)   { $this->_lblposition=$value; }

        function defaultLabelPosition()     { return lpAbove; }

        /**

         * Specifies the distance, in pixels, between the label and the edit region.

         *

         * @return integer

         */

        protected function readLabelSpacing()           { return $this->_lblspacing; }

        protected function writeLabelSpacing($value)    { $this->_lblspacing=$value; }

        function defaultLabelSpacing()      { return 3; }

}

 

class LabeledEdit extends CustomLabeledEdit

{

        //Publish common properties

        function getAlign()             { return $this->readAlign(); }

        function setAlign($value)       { $this->writeAlign($value); }

 

        //function getFont()              { return $this->readFont(); }

        //function setFont($value)        { $this->writeFont($value); }

 

        function getColor()             { return $this->readColor(); }

        function setColor($value)       { $this->writeColor($value); }

 

        function getEnabled()           { return $this->readEnabled(); }

        function setEnabled($value)     { $this->writeEnabled($value); }

 

        function getParentColor()       { return $this->readParentColor(); }

        function setParentColor($value) { $this->writeParentColor($value); }

 

        function getParentFont()        { return $this->readParentFont(); }

        function setParentFont($value)  { $this->writeParentFont($value); }

 

        function getParentShowHint()    { return $this->readParentShowHint(); }

        function setParentShowHint($value) { $this->writeParentShowHint($value); }

 

        function getPopupMenu()         { return $this->readPopupMenu(); }

        function setPopupMenu($value)   { $this->writePopupMenu($value); }

 

        function getShowHint()          { return $this->readShowHint(); }

        function setShowHint($value)    { $this->writeShowHint($value); }

 

        function getVisible()           { return $this->readVisible(); }

        function setVisible($value)     { $this->writeVisible($value); }

 

        //Publish Edit control properties

        function getBorderStyle()       { return $this->readBorderStyle();  }

        function setBorderStyle($value) { $this->writeBorderStyle($value);  }

 

        function getCharCase()          { return $this->readCharCase(); }

        function setCharCase($value)    { $this->writeCharCase($value); }

 

        function getDataField()         { return $this->readDataField(); }

        function setDataField($value)   { $this->writeDataField($value); }

 

        function getDataSource()        { return $this->readDataSource(); }

        function setDataSource($value)  { $this->writeDataSource($value); }

 

        function getIsPassword()        { return $this->readIsPassword(); }

        function setIsPassword($value)  { $this->writeIsPassword($value); }

 

        function getMaxLength()         { return $this->readMaxLength(); }

        function setMaxLength($value)   { $this->writeMaxLength($value); }

 

        function getReadOnly()          { return $this->readReadOnly(); }

        function setReadOnly($value)    { $this->writeReadOnly($value); }

 

        function getText()              { return $this->readText(); }

        function setText($value)        { $this->writeText($value); }

 

        // publish Common Events

        function getjsOnActivate()      { return $this->readjsOnActivate(); }

        function setjsOnActivate($value){ $this->writejsOnActivate($value); }

 

        function getjsOnDeActivate()    { return $this->readjsOnDeActivate(); }

        function setjsOnDeActivate($value) { $this->writejsOnDeActivate($value); }

 

        function getjsOnChange()        { return $this->readjsOnChange(); }

        function setjsOnChange($value)  { $this->writejsOnChange($value); }

 

        function getjsOnBlur()          { return $this->readjsOnBlur(); }

        function setjsOnBlur($value)    { $this->writejsOnBlur($value); }

 

        function getjsOnClick()         { return $this->readjsOnClick(); }

        function setjsOnClick($value)   { $this->writejsOnClick($value); }

 

        function getjsOnContextMenu()   { return $this->readjsOnContextMenu(); }

        function setjsOnContextMenu($value) { $this->writejsOnContextMenu($value); }

 

        function getjsOnDblClick()      { return $this->readjsOnDblClick(); }

        function setjsOnDblClick($value){ $this->writejsOnDblClick($value); }

 

        function getjsOnFocus()         { return $this->readjsOnFocus(); }

        function setjsOnFocus($value)   { $this->writejsOnFocus($value); }

 

        function getjsOnKeyDown()       { return $this->readjsOnKeyDown(); }

        function setjsOnKeyDown($value) { $this->writejsOnKeyDown($value); }

 

        function getjsOnKeyPress()      { return $this->readjsOnKeyPress(); }

        function setjsOnKeyPress($value){ $this->writejsOnKeyPress($value); }

 

        function getjsOnKeyUp()         { return $this->readjsOnKeyUp(); }

        function setjsOnKeyUp($value)   { $this->writejsOnKeyUp($value); }

 

        function getjsOnMouseDown()      { return $this->readjsOnMouseDown(); }

        function setjsOnMouseDown($value){ $this->writejsOnMouseDown($value); }

 

        function getjsOnMouseUp()       { return $this->readjsOnMouseUp(); }

        function setjsOnMouseUp($value) { $this->writejsOnMouseUp($value); }

 

        function getjsOnMouseMove()     { return $this->readjsOnMouseMove(); }

        function setjsOnMouseMove($value) { $this->writejsOnMouseMove($value); }

 

        function getjsOnMouseOut()      { return $this->readjsOnMouseOut(); }

        function setjsOnMouseOut($value) { $this->writejsOnMouseOut($value); }

 

        function getjsOnMouseOver()     { return $this->readjsOnMouseOver(); }

        function setjsOnMouseOver($value) { $this->writejsOnMouseOver($value); }

 

        // publish new properties

        function getEditLabel()             { return $this->readEditLabel(); }

        function setEditLabel($value)       { $this->writeEditLabel($value); }

 

        function getLabelPosition()         { return $this->readLabelPosition(); }

        function setLabelPosition($value)   { $this->writeLabelPosition($value); }

 

        function getLabelSpacing()         { return $this->readLabelSpacing(); }

        function setLabelSpacing($value)   { $this->writeLabelSpacing($value); }

        // publish events

        function getOnClick()           { return $this->readOnClick(); }

        function setOnClick($value)     { $this->writeOnClick($value); }

}

第二、    结合JavaScript来做一些页面特效,这需要JavaScript 比较熟悉的人来完成。我向很多JavaScript程序员手里应该已经积攒了不少非常棒的代码,比如各种按钮、菜单等等。

第三、    结合一些提供特定服务的网站,例如Google Map,或者是图片交流方面的或者是提供一些特定信息的 WebServices网站,例如股票、金融、餐饮服务、新闻、天气、出行信息等内容,作一些能够提供特定信息的组件。这些组件可以从网站得到一些收入。

第四、    将现在比较流行的完整的开源代码拆开,封装成组件包。

第五、    GridReport Chart这三类组件在Delphi VCL里面是种类最丰富的,所以如果能够在这方面及早下手,肯定效果不错。

第六、    调用PHP基础函数,封装一些基础功能也是一个不错的选择,比如 phpinfo()就完全可以进行些方面的封装,FTP和各种文件操作封装的余地也很大。

第七、    编写一些特定的数据库连接和操作的组件应该有不错的应用前景。

第八、    结合Delphi或者是C++ 来编写一些PHP扩展,然后将去封装到VCL中,可以完成一些更强劲的的功能,也更容易让客户认同这些组件的价值。

第九、    编写完整的网站决绝方案,比如门户网站、BBS、商务网站等等。

 

我在这里只是启一个头儿,相信大家能够想出更多更有创意的点子来。

 

 

 

 

 

让我们一起来迎接新控件时代的到来吧!

 

众望所归的Delphi
for PHP
版本终于发布了。

 

现在该软件的试用版本已经可以从网站上下载了,欢迎大家踊跃试用。

 

下载地址:http://www.codegear.com/Downloads/TrialandFreeVersions/Delphi/DelphiforPHP/tabid/250/Default.aspx

CodeGear现在正在就C++Builder未来的开发走向问题,向全世界的开发人员征询意见和建议。截至到目前为止,中国地区的反馈数量非常的少。请大家尽量发动所有的相关程序员去填写调查问卷,千万不要到C++Builder发布的时候才发现这个产品不符合中国程序员的使用习惯或者是对于中国大的软件应用环境支持的不是很好等问题。

 

请大家一定要抓住这个难得的机会,让CodeGear公司的开发团队听到我们的声音。

 

请点击以下链接来提交您的意见:C++Builder 开发者调查问卷 2007 (简体中文)

Google的Blog被国内封杀了

我在Google上的blogcodegear-cn.blogspot.com比较难防分到,所以决定将文章同时发到两个blog上。

如果希望访问我在Google中的blog,请在hosts文件中加一条 codegear-cn.blogspot.com     
72.14.207.190

默认的是72.14.207.191,这个IP被封闭了,所以一定要使用 72.14.207.190

Close Bitnami banner
Bitnami