浅谈前端/软件工程师的代码素养

“程序是写给人读的,只是偶尔让计算机执行一下。”

——Donald Ervin Knuth(高德纳)

关于代码素养

我们常常谈到“素养”一词,是指个人在专业领域内实践训练而成的一种修养,在不同的领域中有不同的体现,如在音乐领域中,“音乐素养”是指个人对于音乐的感觉程度,对音高节奏的把控,对不同流派音乐的鉴赏能力等,而在编程领域,也有不同的素养,反映出对基本功、代码整洁度、专业态度等等方面,所谓“代码素养”,简单来说,就是指代码写的是否优雅美观可维护。

绝对完美的代码是不存在的,代码素养并不是指完美主义。在翻译领域有“信,达,雅”的标准,“雅”之所以放在最后,是因为要达到它,需要有比较高的水准和经验积累。类比到编程领域,我们在编程时,第一时间想到的是如何将业务逻辑实现出来,而不是如何把代码优雅地写出来,所以写代码没有所谓的绝对优雅。但是,作为一名**专业的前端工程师**,确切的说,应该是**专业的软件工程师**,编写优雅的代码应当是时刻保持的追求,它更像是一个准绳,如同每个人知道自己该做什么,不该做什么,所谓原则,所谓底线,体现出所谓的“代码素养”

破窗理论

破窗理论,原义指窗户破损了,建筑无人照管,人们放任窗户继续破损,最终自己也参与破坏活动,在外墙上涂鸦,任垃圾堆积,最后走向倾颓。

破窗理论在实际中非常容易出现,往往第一个人的代码写的不好,第二个人就会有类似“反正他已经写成这样了,那我也只能这样了”的思想,导致代码越维护越冗杂,最后一刻轰然坍塌,变成无人想去维护的垃圾。

整洁的代码

整洁的代码如同优美的散文,试想读过的一本好书,能够随着作者的笔锋跌宕起伏,充满了画面感,调动了自己的喜怒哀乐。代码虽然没有那样的高潮迭起,但整洁的代码应当充满张力,能够在某一时刻利用这种张力将情节推向高潮。

我更喜欢把写代码类比于写文章讲故事,写代码是创作的过程,作者需要将自己想表达的东西通过代码的形式展现出来,而整洁的代码如同讲故事一般,娓娓道来,引人入胜,不好的代码则让人感觉毫无头绪,通篇不知道在讲什么。

整洁代码原则

在现代化的前端开发中,有很多自动化工具可以帮助我们写出规范的代码,如eslinttslint等各种辅助校验工具,知名的规范如google规范airbnb规范等等也从各个细节方面约束,帮助我们形成合理规范的代码风格。

本小节不再重复语言层面的代码风格,根据实际重构项目,总结出一系列开发过程中需要时刻注意的原则,按照重要程度优先级排列。

1. DRY(Don't Repeat Yourself)

相信作为一名软件工程师,大家都听说过最基本的DRY原则,很多设计模式,包括面向对象本身,都是在这条原则上做努力。

DRY顾名思义,是指“不要重复自己”,它实际上强调了一个抽象性原则,如果同样或类似的代码片段出现了两次以上,那么应该将它抽象成一个通用方法或文件,在需要使用的地方去依赖引入,确保在改动的时候,只需调整一处,所有的地方都改变过来,而不是到每个地方去找到相应的代码来修改。

在实际工作中,我见过两种在这条原则上各自走向极端的代码:

  • 一种是完全没有抽象概念,重复的代码散落在各处,更奇葩的是,有一部分的抽象,但更多的是重复,比如在common下抽取了一个data.js的数据处理文件,部分页面中引用了该文件,而更多页面完全拷贝了该文件中的几个不同方法代码。而作者的意图则是令人啼笑皆非——只用到小部分代码,没必要引入那么整个文件。且不论现代化的前端构建层面可以解决这个问题,即使是引入了整个大文件,这部分多余的代码在gzip之后也不会损失多少性能,但这种到处copy的行为带来后续的维护成本是翻倍的。
  • 对于这种行为还遇到另外一个理由,就是工期时间短,改不动之前的代码,怕造成外网问题,那就拷贝一份相同的逻辑来修改。比如支付逻辑,原有的逻辑为单独的UI浮层+单个支付购买,现在产品提出“打包购买”需求,原有的代码逻辑又比较复杂,出现了“改不动”的现象,于是把UI层和购买逻辑的几个文件整个拷贝过来,修改几下,形成了新的“打包购买”模块,后来产品又提出“按条购买”,按照上述”改不动“原则,又拷贝了一份“按条购买”的模块。这样一来调用处的逻辑就会冗余重复,需要根据不同的购买方式引入不同UI组件和支付逻辑,另外如果新添需求,如支持“分期付款”,那么将改动的是非常多的文件,最可悲的是,最后想要把代码重构为一处统一调用的人,将会面对三份“改不动”的压力,需要众多逻辑中对比分析提取相同之处,工作量已经不能用翻倍来衡量,而这种工作量往往无法得到产品的认同和理解。
  • 另一种极端是过度设计,在写每个逻辑的时候都去抽象,让代码的可读性大大下降,一个简单的for循环都要复用,甚至变量定义,这种代码维护起来也是比较有成本的,还有将迥然不同的逻辑过度抽象,使得抽象方法变得非常复杂,经常“牵一发而动全身”,这种行为也是不可取的。

这也是将该原则排在首位的原因,这种行为导致的重构工作量是最大的,保持良好的代码维护性是一种素养,更是一种责任,如果自己在这方面逃避或偷懒,将把这块工作量翻倍地加在将来别人或自己的身上。

2. SRP(Single Responsibility Principle)

SRP也是一个比较著名的设计原则——单一职责,在面向对象的编程中,认为类应该具有单一职责,一个类的改变动机应当只有一个。

对于前端开发来说,最需要贯彻的思想是函数应当保持单一职责,一个函数应当只做一件事,这样一来是保证函数的可复用性,更单一的函数有更强的复用性,二来可以让整体的代码框架更加清晰,细节都封装在一个个小函数中。另外一点也和单一职责有关,就是无副作用的函数,也称纯函数,我们应当尽量保证纯函数的数量,非纯函数是不可避免的,但应当尽量减少它。

把SRP原则排在第二位,因为它非常的重要,没有人愿意看一团乱麻的逻辑,在维护代码时,如果没有一个清晰的逻辑结构,所有的数据定义、数据处理、DOM操作等等一系列细节的代码全部放在一个函数中,导致这个函数非常的冗长,让人本能地产生心理排斥,不愿去查看内部的逻辑。

所有的复杂逻辑放在一个函数中,相信大家看到这样的代码都会眉头一皱:

show: function(a, b) {

    if (!isInit) {

            init();

            isInit = true;

        }



        // reset

        this.balance = 0;

        this.isAllBalance = false;



        var shouldShowLayer = true,

            preSelectedTermId = 0,

            needAddress = course.address\_state, 

            showTerms,

            termsObj;

        var hasPunish = false;



        this.course = course = course || {};

        opt = opt || {};

        opt.showMax = opt.showMax || 6;

        

    (isIosApp || b.isIAP) && (usekedian = !0, priceSymbol = '<i class="icon-font i-kedian"></i>'), 

    f.splice(b.showMax), layer.show({

        $container:b.$container,

        content:termSelectorTpl({

            terms:f,

            curTermId:b.curTermId || d,

            name:a.name,

            hasPunish:h,

            userInfo:j

        }, {

            renderTime:T.render.time.renderCourseTime,

            renderCourseTime:renderCourseTime,

            hideUserInfo:b.hideUserInfo,

            hideTitle:b.hideTitle,

            hidePayPrice:b.hidePayPrice,

            confirmText:b.confirmText,

            sys\_time:a.sys\_time

        }),

        cls:"term-select-new",

        allowMove:function(a) {

             return opt.allowMove || ($target.closest('.select-content').length &&

                        $('.term-select-new .select-time').height() +

                        $('.term-select-new .select-address').height() +

                        $('.term-select-new .select-discounts').height() > (winWidth > 360 ? 190 : winWidth > 320 ? 175 : 150));

        },

        afterInit:function(c) {

           if (needAddress) {

                        that.loadAddress();



                        // 如果需要地址,且是 app 的话,屏幕可见性切换时需要更新下地址

                        if (isApp) {

                            $(document).on(visibilityChange, function (e) {

                                // console.log('visibilityChange',document[hidden]);

                                if (!document[hidden]) {

                                    // true 参数表示必须刷新

                                    that.loadAddress(true);

                                }

                            });

                        }

                    }

                    that.afterTermSelect();



                    $dom.on('click', '.layer-close', function() {

                        setTimeout(function() {

                            !opt.noAutoHide && layer.hide();

                        }, 100);



                        opt.onCancel && opt.onCancel();

                    });



                    $dom.on('click', '.term', function(e) {

                        var $this = $(this);

                        var $terms = $('.term');



                        if (!$this.hasClass('disabled')) {

                            $terms.removeClass('selected');

                            $this.addClass('selected');

                        }



                        that.afterTermSelect();

                    });



                    $dom.on('click', '.layer-comfirm', function(e) {

                        var $this = $(this);

                        var termId = $dom.find('.term.selected').data('term-id');

                        var termName = $dom.find('.term.selected').find('.term-title').html();

                        var discountId = $dom.find('.discounts-list\_item.selected').data('discount-id');

                        var couId = $dom.find('.discounts-list\_item.selected .discounts-coupon').data('cou-id');

                        var directPay = false; 

                        // ios 手Q IAP

                        if (that.toRecharge) {

                            // 需要充值的金额数目

                            var toRechargePrice = that.curPrice - that.balance;

                            if (isIosApp) {

                                require.async('api', function (api) {

                                    api.invoke('api', 'balanceRecharge', {

                                        amount: toRechargePrice

                                    });



                                    // 充值完成设置回调

                                    api.addEventListener('balanceRechargeCallBack', function(data) {

                                        // 支付成功的话

                                        // code=0为成功,其他表示失败

                                        // mode=1表示走充值档位回调,2表示直接充值回调,如果ios 直接充值成功则直接支付

                                        var directPay = data.code === 0 && data.mode === 2;



                                        // 执行回调刷新数据

                                        that.toGetBalance(that.course, termId, function() {

                                            directPay && $this.trigger('click');

                                        });

                                    });

                                });

                            } else {

                                var toRechargePrice = that.curPrice - that.balance;

                                if (that.rechargeMap &&

                                    Object.keys(that.rechargeMap).indexOf("" + toRechargePrice) > -1) {

                                    that.opt.onComfirmClick && that.opt.onComfirmClick(1);

                                    iosPay.iosRecharge({

                                        productId: that.rechargeMap[toRechargePrice],

                                        count: toRechargePrice,

                                        succ: function() {

                                            that.toGetBalance(that.course, $('.term.selected').data('term-id'));

                                        }

                                    });

                                } else {

                                    that.opt.onComfirmClick && that.opt.onComfirmClick(2);

                                    // T.jump('/iosRecharge.html?\_bid=167&\_wv=2147483651');

                                    that.jumpPage('/iosRecharge.html?\_bid=167&\_wv=2147483651');

                                }

                            }

                            return;

                        }



                        if (!termId) {

                            require.async(['modules/tip/tip'], function(Tip) {

                                Tip.show(opt.dialogTitle);

                            });

                            return true;

                        }



                        // check address

                        if (needAddress && !that.addressid) {

                            if (course.must\_fill\_mailing || !$dom.find('.select-address').hasClass('z-no')) {

                                // 没填地址的话地址框要标红,然后需要滑到视窗让用户看到

                                var $cnt = $dom.find('.select-content');

                                var $addressWrap = $dom.find('.select-address\_wrapper').addClass('z-err');

                                var cntRect = $cnt[0].getBoundingClientRect();

                                var addressBoxRect = $addressWrap[0].getBoundingClientRect();

                                // console.log('>>>>> ', cntRect, addressBoxRect);

                                if (addressBoxRect.bottom > cntRect.bottom) {

                                    $cnt.scrollTop($cnt.scrollTop() + (addressBoxRect.bottom - cntRect.bottom));

                                }

                                return;

                            }

                        }



                        if (that.isAllBalance && that.opt.onComfirmClick) {

                            that.opt.onComfirmClick(3);

                        }

                        opt.cb && opt.cb(termId, discountId, couId, termName, that.isAllBalance, that.payBalance, that.addressid);



                        setTimeout(function() {

                            !opt.noAutoHide && layer.hide();

                        }, 300);

                    });



                    $dom.on('click', '.discounts-list\_item', function(e) {

                        var $this = $(this);

                        var $discounts = $('.discounts-list\_item');

                        var isSelected = $this.hasClass('selected');



                        if (!$this.hasClass('disabled')) {

                            $discounts.removeClass('selected');

                            $this.addClass(isSelected ? '' : 'selected');



                            that.setPayPrice();

                        }

                    });



                    $dom.on('click', '.address-person .i-edit2, .address-add', function() {

                        var termId = $dom.find('.term.selected').data('term-id');

                        var courseId = that.course.cid;

                        var src = '/addrEdit.html?\_bid=167&\_wv=2147483649&ns=1&fr=' + (location.pathname.indexOf('allCourse.html') > -1 ?

                            4 : location.pathname.indexOf('courseDetail.html') > -1 ? 2 : 3) + '&course\_id=' + courseId + '&term\_id=' + termId;

                        // T.jump(src);

                        that.jumpPage(src);

                    }).on('click', '.select-address\_title .i-right-light', function(e) {

                        var $addressDom = $dom.find('.select-address');

                        var isOpen = !$addressDom.hasClass('z-no');



                        if (isOpen) {

                            $addressDom.addClass('z-no');

                            that.theAddressid = that.addressid;

                            that.addressid = undefined;

                        } else {

                            $addressDom.removeClass('z-no');

                            that.addressid = that.theAddressid;

                        }

                    });

                }

            });



        } else {

            opt.cb && opt.cb(opt.curTermId || preSelectedTermId);

        }

}

单一职责并不一定要通过很多函数来完成,也可以用分段达到目的,如同这样:

show(data) {

    data && this.setData(data);

    const renderData = {

        data: this.data,

        courseData: this.data.courseData,

        termList: this.termList,

        userInfo: this.userInfo,

        addrList: this.addrList,

        isIAP: this.isIAP,

        balance: betterDisplayNum(this.balance),

        curPrice: betterDisplayNum(this.curPrice),

        curTermId: this.curTermId,

        discountList: this.discountList,

        curDisId: this.curDisId,

        jdSelectId: this.jdSelectId,

        curAddrId: this.curAddrId

    };

    const formatters = {

        // formatters

        termFormatter,

        priceFormatter,

        okBtnFormatter,

        balanceFormatter,

        priceFormatterWithDiscount

    };

    console.log('[render data]: ', renderData);

    const html = payLayerTpl(renderData, formatters);



    // 记录滚动条位置

    this.\_setScrollTop();



    // 防止重复append

    if (this.$view) {

        this.$view.replaceWith(html);

    } else {

        this.$container.append(html);

    }



    afterUIRender(() => {

        this.$view = $('.' + COMPONENT\_NAME).show();        

        this.\_setContentHeight();   // 动态设置滚动区域的高度

        this.\_restoreScrollTop(); // 恢复滚动位置

        this.\_initEvent();   

        this.\_initCountDown();  // 限时折扣倒计时         

    });

}

虽然这个函数也没有维持单一职责,但通过“分段”的形式清晰的表明了内部的流程逻辑,这样的代码看起来就会比所有细节揉在一个函数中好很多。

对于单一职责来说,保持起来还是比较困难的,主要在于职责的拆分,有时过于细致的职责拆分也会给阅读带来比较大的困难,对于这种情况,还是拿写作来对比,单一职责相当于文章的一个“段落”,对于文章来说,每个段落都有它的中心思想,可以用一句话描述出来,如果你发现函数的中心思想很模糊,或者需要很多语言去描述它,那也许它已经有很多个职责该拆分了。

3. LKP(Least Knowledge Principle)

LKP原则是最小知识原则,又称“迪米特”法则,也就是说,一个对象应该对另一个对象有最少的了解,你内部如何复杂都没关系,我只关心调用的地方。

保持暴露接口的简介易用性也是API设计的通用规则,在实际中发现了这样的一个UI组件:

module.exports = {

    show: function(course, opt) {

        // 此处省略一堆逻辑

    },

    jumpPage: function(url) {

        // 此处省略一堆逻辑

    },

    afterTermSelect: function() {

        // 此处省略一堆逻辑

    },

    setPrice: function() {

        // 此处省略一堆逻辑

    },

    setBalance: function() {

        // 此处省略一堆逻辑

    },

    toGetBalance: function(course, curTermId, cb) {

        // 此处省略一堆逻辑

    },

    setDiscounts: function(course, curTermId, curPrice) {

        // 此处省略一堆逻辑

    },

    filterDiscounts: function(discounts, curPrice) {

        // 此处省略一堆逻辑

    },

    isSuitCoupon: function(cou, curPrice) {

        // 此处省略一堆逻辑

    },

    setPayPrice: function() {

        // 此处省略一堆逻辑

    },

    setTermTips: function(wording) {

        // 此处省略一堆逻辑

    },

    loadAddress: function(needUpdate) {

        // 此处省略一堆逻辑

    },

    setAddress: function(addressid) {

        // 此处省略一堆逻辑

    }

}

这个UI组件暴露了非常多的方法,有业务逻辑,有视图逻辑,还有工具方法,这时会给维护者带来比较大的困扰,本能的以为这些暴露出去的方法都在被使用,所以想重构其中某些方法都有些不好下手,而实际上,外部调用的方法仅仅是show而已。

一个好的封装,无论内部多么复杂,它暴露出来的一定是最简洁实用的接口,而内部逻辑是独立维护的,如上述代码,作为一个UI组件来说,提供最基本的show/hide方法即可,有必要时可加入update方法自更新,而无需暴露众多细节,造成调用者和维护者的困扰。

4. 可读性基本定理

可读性基本定理——**“代码的写法应当使别人理解它所需的时间最小化”**。

代码风格和原则不是一概而论的,我们经常需要对一些编码原则和方案进行取舍,例如对于三元表达式的取舍,当我们觉得两种方案都占理时,那么唯一的评判标准就是可读性基本定理,无论写法多么的高超炫技,最好的代码依旧是让人第一时间能够理解的代码。

5. 有意义的名称

代码的可读性绝大部分依赖于变量和函数的命名,一个好的名称能够一针见血地帮助维护者理解逻辑,如同写文章中的“文笔”,文笔优异者总能将故事娓娓道来,引人入胜。

不过要起好名称还是很难的,尤其是我们不是以英语为母语,更是添加了一层障碍,有些人认为纠结在名称上会导致效率变低,开发第一时间应该完成需求的开发。这样说并没有错,我们在开发过程中应当专注于功能逻辑,但不要完全忽视命名,所谓“文笔”是需要锻炼的,思考的越多,命名就会愈加的水到渠成,到后来也就不太会影响工作效率了。

在这里推荐鲍勃大叔提到的童子军规,每一次看自己的代码,都进行一次重构,最简单的重构便是改名,也许一开始觉得命名还比较贴合,但逻辑越写越不符合初始的命名了,当回顾代码时,我们可以顺手对变量和方法进行重新命名,现代编辑工具也很容易做到这一点。

文不对题的命名是最可怕的,如:

function checkTimeConflict(opts) {

    if (opts.param.passcard || (T.bom.get('autopay') && T.bom.get('term\_id'))) { 

        selectToPay({

            result: {}

        }, opts);

    } else {

        DB.checkTimeConflict({

            param: {

                course\_id: opts.param.courseId,

                term\_id: opts.param.termId

            },

            succ: function(data) {

                selectToPay(data, opts);

            },

            err: function(data) {

                dealErr(opts, data);

            }

        });

    }

}

这个函数被命名为check\*开头的,本意是检测课程时间是否冲突,但内部逻辑却包含了支付整个流程,此时对于调用者来说,如果不去细看内部逻辑,很有可能就会错误的认为check函数没有副作用导致事故发生。

6. 适当的注释维护

注释是一个比较有争议性的话题,有人认为可读的函数变量就很清晰,不需要额外的注释,且注释有不可维护性,如:

// 1-PC, 2-android手QH5, 3-android APP, 4-ios&非手QH5, 5-IOS APP

var platform = isAndroidApp ? 3 : isIosApp ? 5 : 4;

实际上,这个字段的含义早已发生了改变,但由于修改者只修改了逻辑,并没有注意到这一行注释,导致这个老注释提供了错误信息,此时的注释不仅变成了无效注释,甚至会导致维护人的误解,造成bug的产生。

对于这种情况,要么维护注释,要么在注释里面注明接口文档,维护文档,在其他情况下,适当的注释是有必要的,对于复杂的逻辑,如果有一个简练的注释,对于代码可读性的帮助是极大的,但有些不必要的注释可以去掉,注释的取舍关键在于可读性基本定理,如:

const filterFn = (term) => {

    if (rule.hideEndTerms && term.is\_end) {

        return false;   // 隐藏已结束的期

    }

    if (rule.hideSignEndTerms && term.is\_out\_of\_date) {

        return false;   // 隐藏已结束报名的期

    }

    if (rule.hideAppliedTerms && courseUtil.isTermApplied(term)) {

        return false;   // 隐藏已报名的期

    }

    if (rule.hideZeroAllowedTerms && courseUtil.isTermZero(term)) {

        return false;   // 隐藏名额已满的期

    }

    if (rule.productType === productType.PACKAGE) {

        return false;   // 隐藏课程包的班级

    }



    return true;

};

对于上述逻辑来说,虽然通过变量可以大致猜出功能含义,但一眼看上去就能清晰掌握逻辑结构,归功于注释的简明与清晰。

小结

本文提到的6个代码编写的原则,前三个偏向于代码维护性,后三个偏向于代码可读性,整个可维护性和可读性构成了代码的基本素养。作为一名前端开发工程师,想要拥有良好的代码素养,首先要让自己的代码可维护,不给别人的维护带来巨大的成本和工作量,其次尽量保证代码的美观可读,整洁的代码人见人爱,如同阅读一本好书,令人心情愉悦。

”代码素养“是一种态度,真正热爱编程的程序员一定不会缺失“代码素养”。我们通常称“写代码”为“程序设计”,而不是“程序编写”,**“设计”**一词体现出了我们的代码是一件作品,也许不如“艺术品”那么精致,但也不是什么粗麻烂布,如果在写代码时天马行空,得过且过,抱着只要能实现功能的思想,那这部“作品“是不具有观赏价值的,这不仅仅体现出代码编写者的”不专业”,更是反映出对待编程这件事的**态度**,代码的整洁程度、可维护性取决于你是否真正“在意”你的代码,每个程序员不一定热爱编程,但请你一定要以“认真”的态度对待自己的专业。

"clean code"的作者鲍勃大叔提到,有人曾送给他一条腕带,上面写着“Test Obsessed”,他发觉自己带上后再也无法取下了,不仅是因为腕带很紧,更是因为它也是一条精神上的紧箍咒。在编程时,我们下意识的看下自己的手腕,是否能发现一条隐形的腕带呢?

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

前端工程

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏我杨某人的青春满是悔恨

如何提高代码品味

写代码虽然大多数时候是个体力活,但不可否认,也需要一点品位。我曾经觉得代码质量很重要,后来写业务写多了,又觉得如果连代码正确都做不到,又谈何代码质量。后来我又醒...

11720
来自专栏恰童鞋骚年

自己动手写游戏:飞机大战

  要说微信中最火爆的小游戏是哪款,可能既不是精心打造的3D大作,也不是《植物大战僵尸2》,而是微信5.0刚开启时的《飞机大战》。

49110
来自专栏程序员互动联盟

八招让你成为C/C++的编程大牛

这个题目的噱头太大,要真的写起来, 足够写一本书了。 本人是过来人, 结合自身的体会和大家交流一下,希望新人能少走弯路。 每个人的情况不一样,我下面的描述可能并...

41660
来自专栏带你撸出一手好代码

细说10月24号为什么是程序员的节日?

今天是10曰24日,有人把这个日子定为程序员的节日,因为1024这个数字和程序员密切相关。 下面我就为大家解密,1024跟程序员有什么关系,程序员写程序又到底是...

32440
来自专栏程序员互动联盟

如何提高编写代码的速度?

如何提高代码编写的速度,一直是一个逃避不了的问题。在天朝你得像打字员一样做程序员,不然老板和上司都觉得你是在玩耍。对项目的贡献体现在哪里?码农难道不是以code...

44380
来自专栏WeTest质量开放平台团队的专栏

浅谈软件工程师的代码素养

“程序是写给人读的,只是偶尔让计算机执行一下。” ——Donald Ervin Knuth(高德纳)

644130
来自专栏Python中文社区

GayHub用户及仓库分析爬虫

專 欄 ❈陈键冬,Python中文社区专栏作者 GitHub: https://github.com/chenjiandongx ❈ 爬虫介绍 Github ...

29870
来自专栏java工会

用JS编写一个Java虚拟机?谈谈哗众取宠的BicaVM

今日目睹某网络新闻,开篇明义便包含如下几行文字 【程序员Artur Ventura,这位超级大牛,用JavaScript写了一个java虚拟机BicaVM】 ...

13000
来自专栏牛客网

CVTE 前端一面面经

【每日一语】在年轻的时候,在那些充满了阳光的长长的下午,我无所事事,也无所惧怕,只因为我知道,在我的生命里有一种永远的等待。挫折会来,也会过去,热泪会流下,也会...

9130
来自专栏老九学堂

如何成为一个牛逼的C/C++程序员?

这个题目的噱头太大,要真的写起来, 足够写一本书了。 老九君分享一些经验,希望能让初学的小伙伴少走弯路。 每个人的情况不一样,所以下面的描述可能并不适合每一个...

35140

扫码关注云+社区

领取腾讯云代金券