PHP最佳实践和技巧

Share

本资源包含由Toptal网络成员提供的PHP最佳实践和PHP技巧的集合.

本资源包含由Toptal网络成员提供的最佳PHP实践和PHP技巧的集合. As such, 本页将定期更新,以包含更多信息并涵盖新兴的PHP技术. 这是一个社区驱动的项目, 所以我们也鼓励你们做出贡献, 我们期待着你们的反馈.

PHP以一种非常快速和肮脏的方式发展, 几乎是作为最受欢迎的web服务器语言的结果. PHP现在具备了您在健壮的语言中寻找的所有东西——它是灵活和动态的. 尽管老版本的PHP并不擅长OOP, 这些语言的新版本几乎支持所有需要的功能, 用一种非常聪明的方式. PHP背后的社区是其成功的主要原因. PHP is used by 81.2% 在所有使用服务器端语言的网站中, 许多伟大的框架的诞生和消亡都是为了弥补语言中缺失的部分. In the same, 他们指导开发所需的部分,以充分利用一门主要用于web开发的语言(尽管你可以做更多)。.

Be aware, PHP是如此容易学习和实现,以至于在不熟练的人手中,当你想构建一个随着时间增长的项目时,它真的很危险. Great PHP developers 知道如何使用它来充分利用这门语言, 包括使用哪些框架和工具, 这样项目就不会成为某人的噩梦. 以下是我们的建议和最佳实践,帮助你使用这门伟大的语言进行开发.

看看Toptal resource pages 以获取有关PHP的更多信息. There is a PHP hiring guide, PHP 工作描述, 常见PHP错误, and PHP 面试问题.

PHP加密容易实现

假设您需要快速加密PHP项目中的一些数据,因为截止日期即将到来. 当有人让你帮他们加密时, 您通常的反应是,糟糕的加密实现可能会产生可怕的后果. 加密应该被认真对待. 适当的研究是必要的,在这个微妙的领域,事情正在迅速变化. 但是截止日期临近了,你需要尽快帮助你的开发者朋友. 我们在这里寻找的是一对很好的通用函数,可以在PHP中简单地加密和解密数据.

我们的Toptal开发人员想出了一个解决方案,用两个自定义函数可以通过web URL接受敏感信息的参数, 或者只能在后端使用. 通过使用键创建IV实现了简单性, 这对安全来说可能不是很好, 但这实际上是保持函数使用简单的好方法.

下面是PHP代码片段,将做有问题的工作:

 '' ){ // decode key and return it back!
	global $key;
	Echo showinfo($key, $val);
	exit();
} elseif ($mod == 'set') && $val <> '' ){ // encode key and return back val encripted!
	global $key;
	返回hideinfo($key, $val);
	exit();
}

完整的代码也可以在 GitHub Gist.

此代码可用于各种情况. 你可以给这个函数传递一个包含自定义数据的数组,如下所示:

Print_r (my_array(), true);

或者它可以用于不同类型的字符编码. 我们希望您会发现它很有用,它将帮助您快速加密您的敏感数据.

Contributors

Kresimir Pendic

所有者和web开发人员(MK dizajn)

Kresimir有超过5年的开发WordPress CMS定制模块(插件)和主题的经验, 线上购物平台, 和前端UI. 众所周知,他会摆弄自定义JavaScript,并严格为前端和移动平台开发. 他的沟通能力非常好,他的奉献精神令人印象深刻.

从Toptal获取最新的开发人员更新.

订阅意味着同意我们的 privacy policy

使用PhpUnit在PHP中使用测试驱动开发

在本文中, 我们将看到如何使用测试驱动开发(TDD), 本文稍后将深入解释)有助于编写可维护的高质量代码, reusable, 并且在发生错误时易于调试和修复.

PHP和测试

PHP和测试一开始并不是朋友. 我们都知道,其他语言从一开始就更倾向于基于测试套件创建代码, 但是PHP是为快速开发而设计的. 这就是为什么测试不是PHP编码人员日常工作的一部分.

但是时代在变, 现在PHP是一种成熟的语言,可以用来构建伟大的大型应用程序, 如果您想在最后节省时间并构建高质量的代码,那么使用测试驱动的开发方法是一种可行的方法.

What Is TDD?

TDD stands for 测试驱动开发. 基本上这意味着:先写测试用例,然后再写代码. 首先,您的测试将失败,因为在这种情况下代码将为空,这很好. 您在这里的任务是通过构建代码使测试通过. 这使您可以将您的编码过程集中在传递描述良好的规范上, 而不是在编码的时候把这些都记在脑子里(当你需要在压力下工作或你累了的时候).

更具体地说,TDD背后的模式可以用以下流程图来总结:

TDD flowchart

所以我总结一下:

  1. 编写测试用例
  2. 为测试代码编写代码
  3. Test
  4. 修复和重构
  5. 移动到下一个测试用例

对于那些跟随我的人 agile approach,这听起来很相似. 是的,因为TDD适合敏捷开发.

Of course, 事情可能会出错, 但是如果您后来发现了一个bug,您就必须编写另一个测试用例, 很明显你的套房不见了. 这样做的好处是,在编写新功能时, 他们还必须通过目前的自动化测试.

Unit Testing

好的,我们知道TDD是什么意思,但是单元测试呢? 单元测试意味着将代码的一部分与其他部分隔离开来进行测试. For that, 您需要改进代码,以便能够单独测试每个组件, 不受其他部件的干扰.

这就是为什么TDD非常适合开发, 因为它迫使开发人员在众所周知的方法中隔离不同的信息片段,这些方法可以单独测试以通过测试.

不难想象,用使用的代码实现TDD会很困难 God objects,或者在一个函数或方法中包含太多的关注点. 例如,处理XML文件并将数据导入数据库的函数. 你可以把它分成几个函数, 处理XML的程序, 另一个用于获取映射到数据库的结果, 另一个用于保存和检查错误. 如果你用一种方法完成所有的任务,那么就很难测试这种方法是否正确地完成了任务. 作为一般规则,每个方法应该只做一件事,并且做得正确.

这就是你如何进行单元测试, 通过创建小而定义良好的方法,并有明确的任务来实现, test, 然后证明是对还是错.

依赖注入

所有这些听起来都很棒, 但是现在,当您需要为测试创建代码时, 你发现你的函数依赖于不同的类, 这些类被实例化到函数中. 你该怎么办?? 你怎么知道问题不在那些课上? 您是否应该为您正在使用的每个库编写一个测试套件?

不,尽管您现在可能想使用带有测试套件的库,不是吗?

这个的解是 依赖注入的模式 控制反转. 所有这些花哨的词语都可以用一个例子来解释,所以让我们以下面的函数为例:

类公司{
	公共成员美元;

	addMember($name, $position)
	{
		$member = new member ($name, $position);
		$this->members[] = $member;
	}
}

在本例中,调用方法 addMember 意味着必须在函数内部创建成员对象. 所以当我们测试这个方法时,我们不仅要测试成员是否被添加到 $members 数组,而且构造函数按预期工作. 不是很单位,对吧??

解决这个问题的方法是在方法调用中注入依赖项:

类公司{
	公共成员美元;

	addMember(Member $ Member)
	{
		$this->members[] = $member;
	}
}

现在我们只需要测试数组是否在添加成员. Having a valid $member 实例不是我们的问题. 所以我们测试这段单独的代码. 这就是单元测试.

在测试用例中,我们可以模拟 $member 争论并传递下去. 但是,有一些框架,比如 Laravel 通过创建动态解析依赖项的方法解决了这个问题, 而不需要在参数中传递它. 当然,这在注入服务提供者时比我们上面的例子更有用, 但是想想这个例子:

类公司{
	sendNotificationEmail()
	{
		//创建$html发送
		$mail = new Mailer();
		$mail->send($html);
	}
}

这里有一个全新的问题,即在方法内部创建mailer函数. 你应该做的是:

类公司{
	sendNotificationEmail(Mailer $mail)
	{
		//创建$html发送
		$mail->send($html);
	}
}

使用服务提供商自动解析自己,你只需要做这个调用:

$company =新公司;
$company->sendNotificationEmail();

这个系统就能做到这一点. 顺便说一下,很好看的语义代码?

引入PHPUnit)

PHPUnit正如您可以通过名称巧妙地推断出来的那样,它是一个用于为我们的代码创建单元测试的库. 这已经包含在一些框架中, 到目前为止,它是PHP中最常用的测试库.

本文的目的是提供改进PHP代码的技巧, 所以我不会详细介绍如何使用PHPUnit. 你可以去他们的详细资料 documentation.

如果你看完这篇文章, 您知道应该编写独立的代码, 这些代码被称为可测试的.

让我们以上面定义的Company示例为例,并为它编写一个测试. 为此,您必须在我们的 phpunit.xml 在我们的项目的根目录中,在我们安装它之后. 我建议使用Composer安装,但您可以下载代码并根据需要包含它. 因此,为了测试add成员函数,我们创建了以下测试:

类CompanyTest扩展PHPUnit_Framework_TestCase
{
	/**
	* @dataProvider memberProvider
	*/
	公共函数testAddMember($company, $member, $expected)
	{
		// Act
		$company->add($member);
		
		// Assert
		$this->assertEquals(count($company->members), $expected);
 	}

	函数memberProvider()
	{
		$company =新公司;

		return [
			[$company,新成员,1],
			[$company,新成员,2],
			[$company,新成员,3],
		];
	}
}

In this case, 我们正在生成带有数据提供者的测试用例,该数据提供者将返回一些信息和预期的结果. 在我们的测试用例中,我们使用这个函数 assertEquals 测试代码是否按预期工作. We have a 有很多断言可供我们使用.

使用PHPUnit,我们可以做很多事情:我们可以模拟对象, databases, 创建数据提供者, 创建测试依赖项, 测试异常, output, and a lot more. 我们还可以通过调用端点来测试REST api. Check the 完整的文档 to learn more.

最后的建议

我对使用TDD的建议是尽可能地使用它. 大多数从头开始的项目都适合TDD方法, 因为你可以随心所欲地创建它们.

However, 代码混乱的项目,或者没有考虑我们在本文中讨论的概念而构建的项目,或者需要在生产环境中快速纠正的项目,并不总是适合的. 在这种情况下,我的建议是使用TDD来重构代码片段,并按部分改进代码. 在大多数情况下,获取所有代码并重新构建所有内容并不是一种选择.

TDD迫使您思考如何构建每个功能,这很好. 随着时间的推移,你会发现这项任务更容易完成, 你的代码将非常容易更新和重构.

Contributors

Franco Risso

自由PHP开发人员

Germany

从ie 5的黑暗时代开始,Franco就一直在开发PHP、MySQL和JavaScript. 他喜欢用REST api将消费者与提供者分开,用TDD来提高代码质量. 他在Node中茁壮成长.前端使用Express和React/Redux等js框架. 他广泛阅读了关于初创企业的文章,并相信它们有潜力推动经济和社会进步. 他是个积极主动的人,不断努力改进自己的工作.

Show More

如何编写无注释的代码?

许多程序员习惯于在他们的代码中添加大量注释. 他们认为这是一种很好的做法, 有一些编程课程鼓励这种方法.

这将导致每个代码块都有大量注释的代码, 替换代码应该说的内容. Toptal开发者, 另一方面, 建议代码应该写得易于阅读和理解, 并且应该表达你想要达到的目标.

在JavaScript世界中,我们看到了几个框架,比如AngularJS和ReactJS, 尽量使代码具有语义性. 为什么PHP或任何后端语言应该是不同的?

开发人员有时会面临快速交付的压力, 在这里或那里发表一些评论是可以的, 向自己和他人解释你在那里做什么. 但是如果你在编写代码时加入一些简单的规则,那就更好了, 将来使用您的代码的每个人都会欣赏它.

为了举例说明,比较下面的代码片段:

/*
这是与一个人相关的所有产品
*/
$res = $obj->query('SELECT ...');
while($row = $res->getNext()){
...
}

with this one:

$args = [
  ‘userId’ => $userId
];
$products = products::getProducts($args);
Foreach ($products作为$product){
...
}

第二种理解和阅读起来更快, 而第一个则暗示先阅读评论, 然后是代码, 这是不好的.

你如何做到这一点? 这里有一些关于如何避免不必要注释的代码过载的建议:

  • 编码规范: 使用通用的方式命名变量, functions, indentation, classes, 还有括号的使用. Take a look at PHP框架互操作组 学习一些推荐的惯例, 试着在你所有的项目中记住并遵循它们.
  • 明确变量的名称: 总是试着用名字来表达你想用它们做什么.
  • 保持函数简短: 在编写函数时,尽量少写几行. 这并不意味着你应该做函数式编程, 但是,请尝试将执行特定任务的代码片段放在代码中, 比如从数据库中获取记录, 在一个单独的函数中.
  • 使用关键词 TODO, FIXME, NOTE 欢迎评论. 使用它们迫使您只在代码中需要完成或修复的地方编写注释.
  • 在文件的开头使用注释: 描述文件的作用, 你不需要注释每个函数,除非你正在编写一个开放的API, 但是对于内部类,尽量只在文档的顶部保留描述. 函数的名称应该表达它的功能. 如果你有带名字的函数 GetProductsByEnabledUsers () 在类中,可能需要将其重构到其他类中.
  • 保持良好的代码结构: 为此,建议使用像Laravel这样的好框架, CakePHP, 和Kohana来保持文件的结构遵循结构化的方法, 遵循一个有意义的模式, like MVC. In general, 如果你有依赖于其他类的类, 尝试创建文件夹来保存这些文件,并避免在文件中使用长名称.
  • 保持良好的缩进: 选择一个惯例,比如4个空格或1个tab,大小为2,但总是使用相同的,缩进好. 其他语言,如Python,只有在使用良好的缩进时才能编译, 许多PHP开发人员认为这是一个缺点. 我个人喜欢Python迫使开发人员保持好的代码.
  • 使用工具来帮助你: 有时候你累了,你只想把它做完. 快速到达终点线往往会让我们的代码变得糟糕. 尝试在代码编辑器中使用一些工具. Toptal开发人员推荐SublimeText和插件Phpcs,它们迫使开发人员遵循标准. 它还有助于在编写代码时学习标准.
  • 使用你的注释作为重构的标志: Sometimes, 当部分功能需要快速完成时, 创建注释来指定期望的最终结果可以作为重构标志,表明这部分代码应该在以后得到改进, 也许可以通过拆分函数或者为变量创建更好的名称.

好吧,我希望您觉得这很有趣和有用,并决定接受代码可读性. 如果你这样做,每个使用你代码的人都会感激你.

Contributors

Franco Risso

自由PHP开发人员

Germany

从ie 5的黑暗时代开始,Franco就一直在开发PHP、MySQL和JavaScript. 他喜欢用REST api将消费者与提供者分开,用TDD来提高代码质量. 他在Node中茁壮成长.前端使用Express和React/Redux等js框架. 他广泛阅读了关于初创企业的文章,并相信它们有潜力推动经济和社会进步. 他是个积极主动的人,不断努力改进自己的工作.

Show More

使用Composer管理PHP中的依赖项

什么是依赖?

PHP是一种创建web项目的优秀语言. 在使用PHP时,您将看到的一件事是,几乎所有您需要的东西都有一个库.

项目中的依赖项是代码用于执行任务的库或包. For example, 如果需要调用REST API,可以使用PHP的curl库, or better yet, 使用Guzzle库, 有创建调用所需的所有东西.

依赖管理的问题

使用第三方代码的一个常见问题是随着时间的推移保持更新. 此外,有些库依赖于其他库,而这些库又依赖于其他库,等等. 在web开发这样的动态环境中,这确实是一个糟糕的情况. 更不用说您将使用哪个版本的库了, 因为有时候你需要根据你所处的环境使用不同的版本, for example, 服务器的PHP版本.

还有一个问题. 需要库是一个很大的痛苦, 您必须将所有包含写入需要使用给定库的文件中. 在删除该类时,必须清除所有包含并删除库. 我们都知道, 这几乎不可能发生, 最后,我们得到了一个大文件夹,里面都是废弃的库, 然后我们需要浪费我们的时间(或者你的团队的时间)来清理所有这些混乱.

认识作曲家,你的新朋友

Composer 是来帮我们解决这些问题的吗. 当我第一次开始使用Composer时,我认为它是一个包管理器,就像Yum或Apt一样. 然而,正如他们在文档中所述, 它们是依赖项管理,而不是包管理器, 因为这些包不是全局安装的. 相反,这些包安装在项目中的供应商文件夹下.

如何管理依赖

我就不一一细说了. 你可以在他们的网站上找到一步一步的指南 入门页,但我会很快解释你需要什么.

首先,您需要安装Composer. 我建议全局安装它,并对其进行设置,以便您可以使用Composer命令. 一旦你这样做了,你必须创建一个 composer.json 文件在项目的根目录下.

如果要将新库添加到项目中, 你所要做的就是把你需要的版本添加到那个文件中, 它看起来像这样:

{
    "require": {
        :“供应商/包1.3.2",
        “供应商/ package2)”:“1.*",
        “供应商/ package3”:“^ 2.0.3"
    }
}

这样做之后,接下来必须在命令行中运行 作曲家安装 和Composer将安装所有需要的库 vendor/ 项目文件夹. If you run 作曲家更新, Composer将检查库的新版本并相应地更新它们.

Also, Composer将安装这些库的依赖项,而不需要您做任何额外的工作.

你可以在Composer中使用很多命令,run 作曲家——帮助 把它们都列出来.

自动装载的类

如果你已经爱上了这个作曲家, 接下来你一定会爱上它的.

Composer为您的类提供了自动加载功能. 您所要做的就是将这一行添加到项目的顶部 需要__DIR__ . ' /供应商/自动装载.php'; 然后调用类的构造函数. 例如,如果你使用Guzzle,你只需要调用:

$client = new GuzzleHttp\ client ();

它会起作用的. 如果您不再需要它,只需删除类,就可以了! 好吧,我撒了谎,这不是全部,但差不多了. 转到下一节学习最后一步.

删除库

我们谈论删除库的痛苦, 因为你不仅要知道这个库,还要知道它的依赖关系, 并且要足够小心,不要删除另一个库正在使用的依赖项.

在composer中,这就像删除依赖一样简单 composer.json file, and run 作曲家更新. 是的,就是这么简单.

认识一下Packagist,项目的存储库

好像你还没受够这一切似的, 有一个公共存储库,用于为您的项目查找库,这些库可以轻松地与Composer一起使用.

Packagist 这是你需要图书馆的地方吗. 在那里,您可以搜索库,并根据您的需要查看要使用的版本. 此外,您可以编写自己的包,并将它们包含在那里供其他人使用.

当然,还有其他方法来包含包,比如直接从Github或其他来源. 这超出了本文的范围,但是您可以在 Composer文档页.

Versioning

当你访问图书馆网站时,你可以看到几个可用的版本. 一般来说,您将有一个准备投入生产,另一个正在开发中.

版本号是如何工作的? 嗯,他们遵循 语义版本标准,其基本内容如下:

有3个数字,中间用一个点隔开. 例如,在你的 composer.json 文件中可以添加如下内容 “guzzlehttp /狂饮”:“5.3.*" 请求Guzzle库. 第一个数字,在这个例子中是5,被称为主版本. 当在库中进行重大更改时,这个数字会发生变化. 更改这个版本可能会更改函数的名称,或者类的名称,或者其他任何东西. 第二个数字是次要版本, 这意味着用于功能更改或添加, 但与其他版本具有向后兼容性. 第三个数字是补丁号. 这个数字在每次修复错误时都会改变,并且不会改变包的功能.

正如您所注意到的,您可以包含通配符 * 来替换这个数字. 这意味着当你跑步的时候 作曲家更新, Composer将检查该类别(major, Minor或patch)并更新到较新的版本. 建议仅使用通配符保留最后一个数字, 并据此更新小版本. However, 如果你真的信任这个库并且总是想要最新的版本, 当然,也可以使用通配符.

还可以使用比较运算符来包含一个范围. For example: "guzzlehttp/guzzle": ">=5.3,<5.4" 等于 “guzzlehttp /狂饮”:“5.3.*",也等于 “guzzlehttp /狂饮”:“~ 5.3" 使用波浪(~) operator.

有关版本控制的更多信息,请参阅Composer的文档 versioning.

节省磁盘空间

最后一个提示,如果您使用Git,请在忽略文件中添加vendor文件夹. 拥有Composer文件就足够了, 因为一旦用户下载了项目, 它只需要运行 作曲家安装 命令获取所有库.

Summary

在本文中,我们解释了什么是依赖关系, 为什么它们对我们的项目很重要, 以及我们在处理这些问题时遇到的问题.

After that, 我们解释了Composer如何以一种非常聪明的方式解决所有这些问题,以保持项目依赖关系的组织.

Composer是另一个可以为您节省大量时间和麻烦的工具.

Contributors

Franco Risso

自由PHP开发人员

Germany

从ie 5的黑暗时代开始,Franco就一直在开发PHP、MySQL和JavaScript. 他喜欢用REST api将消费者与提供者分开,用TDD来提高代码质量. 他在Node中茁壮成长.前端使用Express和React/Redux等js框架. 他广泛阅读了关于初创企业的文章,并相信它们有潜力推动经济和社会进步. 他是个积极主动的人,不断努力改进自己的工作.

Show More

如何在前端和PHP之间上传大块文件?

文件上传本身并不复杂,特别是使用PHP作为后端. 您只需将上传的文件从临时文件夹复制到您想要的位置, 然后你就做完了. 但是,当您希望以块的形式上传更大的文件时,情况就变得更加微妙了.

所以从理论上讲,上传文件块的过程是这样的:

  1. 为前端javascript库启用块模式.
  2. 设置块大小.
  3. 为这个文件上传操作生成一个UID,并将其作为参数发送到后端.
  4. 该文件被切碎,并一块一块地上传到后端PHP. 例如,如果总共有三个块,则将向后端发送三个HTTP请求.
  5. 对于后端接收到的每个数据块,PHP检查它是否是最后一个数据块. If it is not, 它将块文件复制到上传目标文件夹,并使用为这批上传和当前块号生成的UID重命名它, 这样以后就可以识别出这个块,并与其他块正确地重新组装. 如果它是最后一块,它会把所有的碎片组装在一起,我们就搞定了.

足够的理论,让我们展示一些代码:

PHP:

// UID用于识别分块的部分文件,以便我们可以在所有块完成上传后重新组装它们
$uid = $_REQUEST['uid'];
$filename = $_REQUEST['filename'];
if (empty($_FILES['file']['name'])) {
        	return '';
}
if (isset($_POST['_chunkNumber'])) {
        	//文件以块的方式逐块上传
        	$current_chunk_number = $_REQUEST['_chunkNumber'];
        	$chunk_size = $_REQUEST['_chunkSize'];
        	$total_size = $_REQUEST['_totalSize'];
        	
        	$upload_folder = base_path() . 公共/图片/上传/ ' / ';
        	$total_chunk_number = ceil($total_size / $chunk_size);
        	函数(带有_file美元(“文件”)(“tmp_name”),upload_folder美元 . $uid . '.part' . 美元current_chunk_number);
        	//最后一块文件已被接收
        	如果($current_chunk_number == ($total_chunk_number - 1)) {
                    	//将部分文件重新组装成一个完整的文件
                    	for ($i = 0; $i < $total_chunk_number; $i ++) {
                                	$content = file_get_contents($upload_folder . properties . $uid . '.part' . $i);
                                	写入upload_folder美元 . $filename, $content, FILE_APPEND);                                     	分离(upload_folder美元 . $uid . '.part' . $i);
                    	}
        	}
} else {
        	//文件作为一个整体上传,没有chunk模式
        	函数(带有_file美元(“文件”)(“tmp_name”),upload_folder美元 . $filename);
}

JavaScript (AngularJS 1.x and ng-file-upload):

Var 4 = function() {
        	return Math.floor(1 +数学.随机()* 0x10000).toString(16).substring(1);
};
var generateUniqueID = function() {
        	返回s4 () + s4 () + '-' + s4 () + '-' + s4 () + '-' + s4 () + '-' + s4 () + s4 () + s4 ();
};
Upload.upload({
        	url: / upload_image, _
        	//启用块模式,并设置块大小为500KB
        	resumeChunkSize: 500 kb的,
        	data: {
                    	filename: file.name,
                    	file: file,
                    	uid: generateUniqueID ()
        	}
}).然后(函数(响应){
        	console.日志('成功');
}), function(response) {
        	console.log('error');
});

在这个代码示例中,我们使用 ng-file-upload 作为前端上传库. When you var_dump the $_REQUEST 参数,您会注意到参数名为 _chunkSize, _totalSize and _chunkNumber. 有了这些参数, 计算有多少块以及当前块是否是最后一个块是很容易的. 用于其他流行的前端文件上传库, such as Plupload; the parameter names may be different, 但是通过打印出来 $_REQUEST,找到等价物应该不难.

Contributors

Chuoxian Yang

自由PHP开发人员

China

chuxiian是一名自主驱动的全栈开发者和科技公司创始人,拥有近十年的前沿技术开发和满足客户需求的经验. 他完全有能力设计或建造从蓝图到启动的大型项目. 他拥有扎实的计算机科学理论和深厚而广泛的编程知识基础. 此外,他还是一名优秀的沟通者和软件架构师.

Show More

Submit a tip

提交的问题和答案将被审查和编辑, 并可能会或可能不会选择张贴, 由Toptal全权决定, LLC.

*所有字段均为必填项

Toptal连接 Top 3% 世界各地的自由职业人才.

加入Toptal社区.