首页 开发编程 正文

php中怎么添加注释

基本面使用json_encode函数将PHP数组传递给JSON字符串和JavaScript数组。最后展示在JavaScript中用于访问结果数组元素或对象属性的语法。...

php中怎么添加注释,PHP如何将多维数组有效转成JSON?

引言

虚头巴脑的大道理就不讲了,直接上代码。

我们说一下把PHP嵌套的数组,转换为JSON字符串,提供给JS前端用。

基本面

使用json_encode函数将PHP数组传递给JSON字符串和JavaScript数组。在本文,我们使用多维数组进行演示。一个数组是数字索引的,另外两个是混合数字和字典关联的。

无论PHP数组是单级的还是多级的,或者是数字索引的还是关联索引的,下面的代码放在一个JavaScript段中,将会输出到JavaScript:

你可能希望使用JavaScript的JSON。解析来处理PHP的json_encode的结果,在这种情况下,使用以下代码:

对于下面的示例数组,首先用PHP显示该数组,然后包括用于输出它的json_encode的JavaScript段,然后显示输出。最后展示在JavaScript中用于访问结果数组元素或对象属性的语法。

示例数组1

默认情况下,json_encode将数字索引的PHP数组输出为JavaScript中的数组文本。关联的PHP数组作为对象文本输出。

示例数组2

在json_encode输出中,外层是数组文字,而第二层形成对象文字。下面的代码注释演示了如何使用JSON_PRETTY_PRINT选项和json_encode实现更可读的输出:

更复杂的数组结构

这个例子演示了如何使用JSON。解析json_encode的输出。PHP标记为此用单引号括起来。

访问方式可以是索引键,可以是数字。

我们不能在JSON.parse中使用JSON_PRETTY_PRINT,因为那会导致JavaScript错误:未终止的字符串文字。

写在最后

本文通过深度对比PHP的数组,与JS的JSON对象之间的数值转换,可以较为清晰地掌握关键点,而不至于频频踩坑。

Happy coding :)

我是@程序员小助手,持续分享编程知识,欢迎关注。

iphone4通知栏显示农历插件安装技巧?

在桌面中打开设置,然后选择邮件,通讯录,日历选项,之后在账户选项中,添加账户,还要在账户信息里找到其他,点击进入,选择日历设置,选择已添加订阅的日历,再选项输入服务器地址输入:iweek.me/m/ical/nong.php?starty=2017&county=2(代表订阅2017-2019的农历),之后就需要验证服务器信息,添加完成后,就可以看到订阅账户信息成功,日历就会显示农历的呢。 备注:上面的2017代表从那一年开始,2代表间隔时间为两年,这个数字可以根据自身的需要进行修改。

用爬虫技术能做到哪些有趣的事情?

看到这个问题必须来怒答一波~用python爬虫爬便宜机票了解一下?

喜欢旅行又怕吃土?让Python来爬取最便宜机票吧!

图源:

videoblocks.com

你喜欢旅行吗?

这个问题通常会得到一个肯定的答案,随后引出一两个有关之前冒险经历的故事。大多数人都认为旅行是体验新文化和开阔视野的好方法。但如果问题是“你喜欢搜索机票的过程吗?”也许话题就到此为止了……

可事实上,便宜的机票往往也很重要!本文将尝试构建一个网络爬虫,该爬虫对特定目的地运行并执行带有浮动日期(首选日期前后最多三天)的航班价格搜索。它会将结果保存为excel文件并发送一封包含快速统计信息的电子邮件。显然,这个爬虫的目的就是帮助我们找到最优惠的价格!

你可以在服务器上运行脚本(一个简单的Raspberry Pi就可以),每天运行一到两次。结果会以邮件形式发送,建议将excel文件存入Dropbox文件夹,以便随时随地查看。

因为爬虫以“浮动日期”进行搜索,所以它会搜索首选日期前后最多三天的航班信息。尽管该脚本一次仅运行一对目的地,但可以很容易地改写该爬虫使其每个循环运行多个目的地。最终甚至可能找到一些错误票价...那会很有意思!

另一个爬虫

某种意义上来讲,网络爬取是互联网“工作”的核心。

也许你认为这是一个十分大胆的说法,但谷歌就是从拉里·佩奇用Java和Python构建的网络爬虫开始的。爬虫不断地爬取信息,整个互联网都在试图为所有问题提供最佳的可能答案。网络爬取有不计其数的应用程序,即使更喜欢数据科学中的其他分支,你仍需要一些爬取技巧以获得数据。

这里用到的一些技术来自于最近新的一本佳作《Python网络数据采集》,书中包含与网络爬取相关的所有内容,并提供了大量简例和实例。甚至有一个特别有意思的章节,讲述如何解决验证码检验的问题。

Python的拯救

第一个挑战就是选择爬取信息的平台,本文选择了客涯(Kayak)。我们试过了Momondo, 天巡(Skyscanner), 亿客行(Expedia)和其它一些网站,但是这些网站上的验证码特别变态。

在那些“你是人类吗?”的验证中,尝试了多次选择交通灯、十字路口和自行车后,客涯似乎是最好的选择,尽管短时间内加载太多页面它会跳出安全检查。

我们设法让机器人每4到6个小时查询一次网站,结果一切正常。虽然说不定哪个部分偶尔会出点小问题,但是如果收到验证码,既可以手动解决问题后启动机器人,也可以等待几小时后的自动重启。

如果你是网络爬取新手,或者不知道为何有些网站花费很大力气阻止网络爬取,那么为构建爬虫写下第一行代码前,你一定要多加努力。

谷歌的“网络爬取规范”:

http://lmgtfy.com/?q=web+scraping+etiquette

系紧安全带...

导入并打开Chrome浏览器标签页后,会定义一些循环中会用到的函数。这个架构的构思大概是这样的:

· 一个函数用于启动机器人程序,表明想要搜索的城市和日期。

· 这个函数获得首轮搜索结果,按“最佳”航班排序,然后点击“加载更多结果”。

· 另一个函数会爬取整个页面,并返回一个dataframe数据表。

· 随后重复步骤2和步骤3,得出按“价格”和“航行时间”排序的结果。

· 发送一封简要总结价格(最低价和平均价)的邮件,并将带有这三种排序类型的dataframe数据表保存为一份excel文件。

· 以上所有步骤会在循环中重复,每X小时运行一次。

每个Selenium项目都以一个网页驱动器开始。我们使用Chromedriver驱动器,但还有其它选择。PhantomJS和Firefox也很受欢迎。下载Chromedriver后,将其置于一个文件夹中即可。第一行代码会打开一个空白Chrome标签页。

from time import sleep, strftime

from random import randint

import pandas as pd

from selenium import webdriver

from selenium.webdriver.common.keys import Keys

import smtplib

from email.mime.multipart import MIMEMultipart

# Change this to your own chromedriver path!

chromedriver_path = 'C:/{YOUR PATH HERE}/chromedriver_win32/chromedriver.exe'

driver = webdriver.Chrome(executable_path=chromedriver_path) # This will open the Chrome window

sleep(2)

这些是将用于整个项目的包。使用randint函数令机器人在每次搜索之间随机睡眠几秒钟。这对任何一个机器人来说都是必要属性。如果运行前面的代码,应该打开一个Chrome浏览器窗口,机器人会在其中导航。

一起来做一个快速测试:在另一个窗口上访问客涯网(http://kayak.com),选择往返城市和日期。选择日期时,确保选择的是“+-3天”。由于在编写代码时考虑到了结果页面,所以如果只想搜索特定日期,很可能需要做一些微小的调整。

点击搜索按钮在地址栏获取链接。它应该类似于下面所使用的链接,将变量kayak定义为url,并从网页驱动器执行get方法,搜索结果就会出现。

无论何时,只要在几分钟内使用get命令超过两到三次,就会出现验证码。实际上可以自己解决验证码,并在下一次验证出现时继续进行想要的测试。从测试来看,第一次搜索似乎一直没有问题,所以如果想运行这份代码,并让它在较长的时间间隔后运行,必须解决这个难题。你并不需要十分钟就更新一次这些价格,对吧?

每个XPath都有陷阱

到目前为止,已经打开了一个窗口,获取了一个网站。为了开始获取价格和其他信息,需要使用XPath或CSS选择器,我们选择了XPath。使用XPath导航网页可能会令人感到困惑,即使使用从inspector视图中直接使用“复制XPath”,但这不是获得所需元素的最佳方法。有时通过“复制XPath”这个方法获得的链接过于针对特定对象,以至于很快就失效了。《Python网络数据采集》一书很好地解释了使用XPath和CSS选择器导航的基础知识。

接下来,用Python选择最便宜的结果。上面代码中的红色文本是XPath选择器,在网页上任意一处右键单击选择“inspect”就可以看到它。在想要查看代码的位置,可以再次右键单击选择“inspect”。

为说明之前所观察到的从“inspector”复制路径的缺陷,请参考以下差异:

1 # This is what the copymethod would return. Right click highlighted rows on the right side and select “copy> Copy XPath”//*[@id=“wtKI-price_aTab”]/div[1]/div/div/div[1]/div/span/span

2 # This is what I used todefine the “Cheapest” buttoncheap_results= ‘//a[@data-code = “price”]’

第二种方法的简洁性清晰可见。它搜索具有data-code等于price属性的元素a。第一种方法查找id等于wtKI-price_aTab的元素,并遵循第一个div元素和另外四个div和两个span。这次……会成功的。现在就可以告诉你,id元素会在下次加载页面时更改。每次页面一加载,字母wtKI会动态改变,所以只要页面重新加载,代码就会失效。花些时间阅读XPath,保证你会有收获。

不过,使用复制的方法在不那么“复杂”的网站上工作,也是很好的!

基于以上所展示的内容,如果想在一个列表中以几个字符串的形式获得所有搜索结果该怎么办呢?其实很简单。每个结果都在一个对象中,这个对象的类是“resultWrapper”。获取所有结果可以通过像下面这样的for循环语句来实现。如果你能理解这一部分,应该可以理解接下来的大部分代码。它基本上指向想要的结果(结果包装器),使用某种方式(XPath)获得文本,并将其放置在可读对象中(首先使用flight_containers,然后使用flight_list)。

前三行已展示在图中,并且可以清楚地看到所需的内容,但是有获得信息的更优选择,需要逐一爬取每个元素。

准备起飞吧!

最容易编写的函数就是加载更多结果的函数,所以代码由此开始。为了在不触发安全验证的前提下最大化所获取的航班数量,每次页面显示后,单击“加载更多结果”。唯一的新内容就是所添加的try语句,因为有时按钮加载会出错。如果它对你也有用,只需在前面展示的start_kayak函数中进行简要注释。

# Load more results to maximize the scraping

def load_more():

try:

more_results = '//a[@class = “moreButton”]'

driver.find_element_by_xpath(more_results).click()

# Printing these notes during the program helps me quickly check what it is doing

print('sleeping…..')

sleep(randint(45,60))

except:

pass

现在,经过这么长的介绍,已经准备好定义实际爬取页面的函数。

我们编译了下一个函数page_scrape中的大部分元素。有时这些元素会返回列表插入去程信息和返程信息之间。这里使用了一个简单的办法分开它们,比如在第一个 section_a_list和section_b_list变量中,该函数还返回一个flight_df数据表。所以可以分离在不同分类下得到的结果,之后再把它们合并起来。

def page_scrape():

“““This function takes care of the scraping part”““

xp_sections = '//*[@class=“section duration”]'

sections = driver.find_elements_by_xpath(xp_sections)

sections_list = [value.text for value in sections]

section_a_list = sections_list[::2] # This is to separate the two flights

section_b_list = sections_list[1::2] # This is to separate the two flights

# if you run into a reCaptcha, you might want to do something about it

# you will know there's a problem if the lists above are empty

# this if statement lets you exit the bot or do something else

# you can add a sleep here, to let you solve the captcha and continue scraping

# i'm using a SystemExit because i want to test everything from the start

if section_a_list == []:

raise SystemExit

# I'll use the letter A for the outbound flight and B for the inbound

a_duration = []

a_section_names = []

for n in section_a_list:

# Separate the time from the cities

a_section_names.append(''.join(n.split()[2:5]))

a_duration.append(''.join(n.split()[0:2]))

b_duration = []

b_section_names = []

for n in section_b_list:

# Separate the time from the cities

b_section_names.append(''.join(n.split()[2:5]))

b_duration.append(''.join(n.split()[0:2]))

xp_dates = '//div[@class=“section date”]'

dates = driver.find_elements_by_xpath(xp_dates)

dates_list = [value.text for value in dates]

a_date_list = dates_list[::2]

b_date_list = dates_list[1::2]

# Separating the weekday from the day

a_day = [value.split()[0] for value in a_date_list]

a_weekday = [value.split()[1] for value in a_date_list]

b_day = [value.split()[0] for value in b_date_list]

b_weekday = [value.split()[1] for value in b_date_list]

# getting the prices

xp_prices = '//a[@class=“booking-link”]/span[@class=“price option-text”]'

prices = driver.find_elements_by_xpath(xp_prices)

prices_list = [price.text.replace('$','') for price in prices if price.text != '']

prices_list = list(map(int, prices_list))

# the stops are a big list with one leg on the even index and second leg on odd index

xp_stops = '//div[@class=“section stops”]/div[1]'

stops = driver.find_elements_by_xpath(xp_stops)

stops_list = [stop.text[0].replace('n','0') for stop in stops]

a_stop_list = stops_list[::2]

b_stop_list = stops_list[1::2]

xp_stops_cities = '//div[@class=“section stops”]/div[2]'

stops_cities = driver.find_elements_by_xpath(xp_stops_cities)

stops_cities_list = [stop.text for stop in stops_cities]

a_stop_name_list = stops_cities_list[::2]

b_stop_name_list = stops_cities_list[1::2]

# this part gets me the airline company and the departure and arrival times, for both legs

xp_schedule = '//div[@class=“section times”]'

schedules = driver.find_elements_by_xpath(xp_schedule)

hours_list = []

carrier_list = []

for schedule in schedules:

hours_list.append(schedule.text.split('\n')[0])

carrier_list.append(schedule.text.split('\n')[1])

# split the hours and carriers, between a and b legs

a_hours = hours_list[::2]

a_carrier = carrier_list[1::2]

b_hours = hours_list[::2]

b_carrier = carrier_list[1::2]

cols = (['Out Day', 'Out Time', 'Out Weekday', 'Out Airline', 'Out Cities', 'Out Duration', 'Out Stops', 'Out Stop Cities',

'Return Day', 'Return Time', 'Return Weekday', 'Return Airline', 'Return Cities', 'Return Duration', 'Return Stops', 'Return Stop Cities',

'Price'])

flights_df = pd.DataFrame({'Out Day': a_day,

'Out Weekday': a_weekday,

'Out Duration': a_duration,

'Out Cities': a_section_names,

'Return Day': b_day,

'Return Weekday': b_weekday,

'Return Duration': b_duration,

'Return Cities': b_section_names,

'Out Stops': a_stop_list,

'Out Stop Cities': a_stop_name_list,

'Return Stops': b_stop_list,

'Return Stop Cities': b_stop_name_list,

'Out Time': a_hours,

'Out Airline': a_carrier,

'Return Time': b_hours,

'Return Airline': b_carrier,

'Price': prices_list})[cols]

flights_df['timestamp'] = strftime(“%Y%m%d-%H%M”) # so we can know when it was scraped

return flights_df

尽量让这些名字容易理解。记住变量a表示旅行的去程信息,变量b表示旅行的返程信息。接下来说说下一个函数。

等等,还有什么吗?

截至目前,已经有了一个能加载更多结果的函数和一个能爬取其他结果的函数。本可以在此结束这篇文章,而你可以自行手动使用这些函数,并在浏览的页面上使用爬取功能。但是前文提到给自己发送邮件和一些其他信息的内容,这都包含在接下来的函数start_kayak中。

它要求填入城市名和日期,并由此打开一个kayak字符串中的地址,该字符串直接跳转到“最佳”航班结果排序页面。第一次爬取后,可以获取价格的顶部矩阵,这个矩阵将用于计算平均值和最小值,之后和客涯(Kayak)的预测结果(页面左上角)一同发送到邮件中。这是单一日期搜索时可能导致错误的原因之一,因其不包含矩阵元素。

def start_kayak(city_from, city_to, date_start, date_end):

“““City codes it's the IATA codes!

Date format YYYY-MM-DD”““

kayak = ('https://www.kayak.com/flights/' + city_from + '-' + city_to +

'/' + date_start + '-flexible/' + date_end + '-flexible?sort=bestflight_a')

driver.get(kayak)

sleep(randint(8,10))

# sometimes a popup shows up, so we can use a try statement to check it and close

try:

xp_popup_close = '//button[contains(@id,”dialog-close”) and contains(@class,”Button-No-Standard-Style close “)]'

driver.find_elements_by_xpath(xp_popup_close)[5].click()

except Exception as e:

pass

sleep(randint(60,95))

print('loading more.....')

# load_more()

print('starting first scrape.....')

df_flights_best = page_scrape()

df_flights_best['sort'] = 'best'

sleep(randint(60,80))

# Let's also get the lowest prices from the matrix on top

matrix = driver.find_elements_by_xpath('//*[contains(@id,”FlexMatrixCell”)]')

matrix_prices = [price.text.replace('$','') for price in matrix]

matrix_prices = list(map(int, matrix_prices))

matrix_min = min(matrix_prices)

matrix_avg = sum(matrix_prices)/len(matrix_prices)

print('switching to cheapest results…..')

cheap_results = '//a[@data-code = “price”]'

driver.find_element_by_xpath(cheap_results).click()

sleep(randint(60,90))

print('loading more…..')

# load_more()

print('starting second scrape…..')

df_flights_cheap = page_scrape()

df_flights_cheap['sort'] = 'cheap'

sleep(randint(60,80))

print('switching to quickest results…..')

quick_results = '//a[@data-code = “duration”]'

driver.find_element_by_xpath(quick_results).click()

sleep(randint(60,90))

print('loading more…..')

# load_more()

print('starting third scrape…..')

df_flights_fast = page_scrape()

df_flights_fast['sort'] = 'fast'

sleep(randint(60,80))

# saving a new dataframe as an excel file. the name is custom made to your cities and dates

final_df = df_flights_cheap.append(df_flights_best).append(df_flights_fast)

final_df.to_excel('search_backups//{}_flights_{}-{}_from_{}_to_{}.xlsx'.format(strftime(“%Y%m%d-%H%M”),

city_from, city_to,

date_start, date_end), index=False)

print('saved df…..')

# We can keep track of what they predict and how it actually turns out!

xp_loading = '//div[contains(@id,”advice”)]'

loading = driver.find_element_by_xpath(xp_loading).text

xp_prediction = '//span[@class=“info-text”]'

prediction = driver.find_element_by_xpath(xp_prediction).text

print(loading+'\n'+prediction)

# sometimes we get this string in the loading variable, which will conflict with the email we send later

# just change it to “Not Sure” if it happens

weird = '¯\\_(ツ)_/¯'

if loading == weird:

loading = 'Not sure'

username = 'YOUREMAIL@hotmail.com'

password = 'YOUR PASSWORD'

server = smtplib.SMTP('smtp.outlook.com', 587)

server.ehlo()

server.starttls()

server.login(username, password)

msg = ('Subject: Flight Scraper\n\n\

Cheapest Flight: {}\nAverage Price: {}\n\nRecommendation: {}\n\nEnd of message'.format(matrix_min, matrix_avg, (loading+'\n'+prediction)))

message = MIMEMultipart()

message['From'] = 'YOUREMAIL@hotmail.com'

message['to'] = 'YOUROTHEREMAIL@domain.com'

server.sendmail('YOUREMAIL@hotmail.com', 'YOUROTHEREMAIL@domain.com', msg)

print('sent email…..')

虽然没有使用Gmail账户测试发送邮件,但是可以搜索到很多的替代方法,前文提到的那本书中也有其他方法来实现这一点。如果已有一个Hotmail账户,只要替换掉个人的详细信息,它就会开始工作了。

如果想探索脚本的某一部分正在做什么,可以将脚本复制下来并在函数外使用它。这是彻底理解它的唯一方法。

利用刚才创造的一切

在这些步骤之后,还可以想出一个简单的循环来使用刚创造的函数,同时使其持续运行。完成四个“花式”提示,写下城市和日期(输入)。因为测试时不想每次都输入这些变量,需要的时候可以使用以下这个清楚的方式进行替换。

如果已经做到了这一步,恭喜你!改进还有很多,比如与Twilio集成,发送文本消息而不是邮件。也可以使用VP*或更加难懂的方式同时从多个服务器上研究搜索结果。还有就是验证码的问题,验证码会时不时地跳出来,但对此类问题还是有解决办法的。不过,能走到这里已经是有很牢固的基础了,你可以尝试添加一些额外的要素。

使用脚本运行测试的示例

留言 点赞 关注

我们一起分享AI学习与发展的干货

欢迎关注全平台AI垂类自媒体 “读芯术”

如何提升网站的打开速度?

网站速度优化如何影响转化

缓慢的速度确实会扼杀转化。实际上有47%的消费者希望网站在两秒钟或更短的时间内加载完毕, 而40%的消费者会放弃耗时三秒或更长时间的页面。

这意味着如果您的网站加载时间超过三秒钟,那么您将失去几乎一半的访问者,甚至无法访问他们。

仅此一项就对您潜在的转化带来了巨大的打击。然而对于决定留下来的访问者,缓慢的加载时间可以阻止他们将来返回。在一项调查中,有79%的客户表示他们不会返回效果不佳的网站。

最好的例子之一是沃尔玛提高网站速度后的转化率和收入提高。在最初的分析中,他们发现转换的访问者收到的页面加载速度是未转换的访问者的两倍。这表明页面越快,访问者进行购物的可能性就越大。

在网站速度优化结束时,沃尔玛报告了以下结果:

网站速度每提高一秒钟,转化率就会提高2%。每提高100毫秒,他们的增量收入就增加了1%。

在另一项研究中,加载时间与转化率之间的关系表明 ,加载时间仅增加一秒钟,转化率就会降低25%。那我们来看看如何提升网站的打开速度?

最小化HTTP请求

根据Yahoo的说法,网页加载时间的80% 用于下载网页的不同部分,例如图像,样式表和脚本。

这些元素中的每个元素都会发出HTTP请求,因此页面上的组件越多,页面呈现所花费的时间就越长。

最小化您的请求的第一步是弄清楚您的站点当前有多少,以用作基准。如果您使用的是Google Chrome浏览器,则可以使用浏览器的开发人员工具查看您的网站发出了多少个HTTP请求。

右键单击要分析的页面,然后单击“检查”,然后单击“网络”选项卡。(如果看不到“网络”标签,则可能需要通过将左侧边框拖动到左侧来展开“开发人员工具”侧边栏。)

“名称”列显示页面上的所有文件,“大小”列显示每个文件的大小,“时间”列显示加载每个文件需要多长时间。

在左下角,您还将看到该网站发出的请求总数。减少此请求数量将加快您的网站速度,浏览文件并查看是否有必要。

缩小并合并文件

现在您已经知道您的网站发出了多少请求,接下来就可以减少该请求。最好的入门指南是HTML,CSS和JavaScript文件。

这些是非常重要的文件,因为它们决定了网站的外观。它们还会增加您的网站在用户每次访问时所发出的请求数量。您可以通过“缩小”并合并文件来减少此数量。这样可以减少每个文件的大小以及文件的总数。

如果您使用模板化的网站构建器,这尤其重要。这些使构建网站变得容易,但有时会创建混乱的代码,从而大大降低您的网站速度。

缩小文件涉及删除不必要的格式,空格和代码。由于每段不必要的代码都会增加页面的大小,因此消除多余的空格,换行和缩进非常重要。这样可以确保您的页面尽可能的简洁。

组合文件确实是听起来很容易解决的问题。如果您的站点运行多个CSS和JavaScript文件,就可以将它们组合为一个文件。

当涉及到您的网站时,精益求精是更好的选择。页面上的元素越少,浏览器使页面呈现所需的HTTP请求就越少-加载速度就越快。

对CSS和JavaScript文件使用异步加载

缩小并合并了一些文件后,您还可以优化它们在页面上的加载方式。

可以通过两种不同的方式加载CSS和JavaScript之类的脚本:同步或异步加载。

如果您的脚本是同步加载的,则它们会按照在页面上显示的顺序一次加载一个。另一方面,如果脚本是异步加载的,则其中一些脚本将同时加载。

异步加载文件可以加快页面的速度,因为当浏览器加载页面时,页面从上到下移动。

如果到达非异步的CSS或JavaScript文件,它将停止加载,直到完全加载该特定文件为止。如果该文件是异步文件,则浏览器可以继续同时加载页面上的其他元素。

推迟JavaScript加载

延迟文件意味着阻止其加载,直到其他元素加载完毕。如果推迟使用较大的文件(例如JavaScript),则确保可以立即加载其余内容。

如果您有HTML网站,则需要在</body>标记之前放置一个外部JavaScript文件的调用,该文件如下所示:

如果您网站的速度尚未达到您想要的速度,那么这可能是一个可以持续进行的目标。但是当您进行本文中列出的更改时,您可以继续测试您的网站并提高排名,用了以上这些方法后,肯定速度会提高不少。

什么样的代码叫好代码?

送大家以下java学习资料

简介: 我们每天都与代码打交道,但当被问道什么是好的代码时,很多人可能会先愣一下,然后给出的回答要么比较空泛,要么比较散,没办法简单明了地概括出来。显然,这个问题并没有唯一的标准答案,谁都可以谈论自己的理解,今天谈谈我对于好代码的理解。

我们每天都与代码打交道,但当被问道什么是好的代码时,很多人可能会先愣一下,然后给出的回答要么比较空泛,要么比较散,没办法简单明了地概括出来。显然,这个问题并没有唯一的标准答案,谁都可以谈论自己的理解,今天谈谈我对于好代码的理解。

一句话概括

衡量代码质量的唯一有效标准:WTF/min —— Robert C. Martin

Bob大叔对于好代码的理解非常有趣,对我也有很大的启发。我们编写的代码,除了用于机器执行产生我们预期的效果以外,更多的时候是给人读的,这个读代码的可能是后来的维护人员,更多时候是一段时间后的作者本人。

我敢打赌每个人都遇到过这样的情况:过几周或者几个月之后,再看到自己写的代码,感觉一团糟,不禁怀疑人生。

我们自己写的代码,一段时间后自己看尚且如此,更别提拿给别人看了。

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。—— Martin Fowler

所以,谈到好代码,首先跳入自己脑子里的一个词就是:整洁。

好的代码一定是整洁的,给阅读的人一种如沐春风,赏心悦目的感觉。

整洁的代码如同优美的散文。—— Grady Booch

好代码的特性

很难给好的代码下一个定义,相信很多人跟我一样不会认为整洁的代码就一定是好代码,但好代码一定是整洁的,整洁是好代码的必要条件。整洁的代码一定是高内聚低耦合的,也一定是可读性强、易维护的。

高内聚低耦合

高内聚低耦合几乎是每个程序员员都会挂在嘴边的,但这个词太过于宽泛,太过于正确,所以聪明的编程人员们提出了若干面向对象设计原则来衡量代码的优劣:

开闭原则 OCP (The Open-Close Principle)单一职责原则 SRP (Single Responsibility Principle)依赖倒置原则 DIP (Dependence Inversion Principle)

最少知识原则 LKP (Least Knowledge Principle)) / 迪米特法则 (Law Of Demeter)

里氏替换原则 LSP (Liskov Substitution Principle)接口隔离原则 ISP (Interface Segregation Principle)组合/聚合复用原则 CARP (Composite/Aggregate Reuse Principle)

这些原则想必大家都很熟悉了,是我们编写代码时的指导方针,按照这些原则开发的代码具有高内聚低耦合的特性。换句话说,我们可以用这些原则来衡量代码的优劣。

但这些原则并不是死板的教条,我们也经常会因为其他的权衡(例如可读性、复杂度等)违背或者放弃一些原则。比如子类拥有特性的方法时,我们很可能打破里氏替换原则。再比如,单一职责原则跟接口隔离原则有时候是冲突的,我们通常会舍弃接口隔离原则,保持单一职责。只要打破原则的理由足够充分,也并不见得是坏的代码。

可读性

代码只要具有了高内聚和低耦合就足够好了吗?并不见得,我认为代码还必须是易读的。好的代码无论是风格、结构还是设计上都应该是可读性很强的。可以从以下几个方面考虑整洁代码,提高可读性。

命名

大到项目名、包名、类名,小到方法名、变量名、参数名,甚至是一个临时变量的名称,其命名都是很严肃的事,好的名字需要斟酌。

► 名副其实

好的名称一定是名副其实的,不需要注释解释即可明白其含义的。

/** * 创建后的天数 **/ int d; int daysSinceCreation;

后者比前者的命名要好很多,阅读者一下子就明白了变量的意思。

► 容易区分

我们很容易就会写下非常相近的方法名,仅从名称无法区分两者到底有啥区别(eg. getAccount()与getAccountInfo()),这样在调用时也很难抉择要用哪个,需要去看实现的代码才能确定。

► 可读的

名称一定是可读的,易读的,最好不要用自创的缩写,或者中英文混写。

► 足够短

名称当然不是越长越好,应该在足够表达其含义的情况下越短越好。

格式

良好的代码格式也是提高可读性非常重要的一环,分为垂直格式和水平格式。

► 垂直格式

通常一行只写一个表达式或者子句。一组代码代表一个完整的思路,不同组的代码中间用空行间隔。

public class Demo { @Resource private List<Handler> handlerList; private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>(); @PostConstruct private void init() { if (!CollectionUtils.isEmpty(handlerList)) { for (Handler handler : handlerList) { handlerMap.put(handler.getType(), handler); } } } publicResult<Map<String, Object>> query(Long id, TypeEnum typeEnum) { Handler handler = handlerMap.get(typeEnum); if (null == handler) { return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE); } return handler.query(id); } }

如果去掉了空行,可读性大大降低。

public class Demo { @Resource private List<Handler> handlerList; private Map<TypeEnum, Handler> handlerMap = new ConcurrentHashMap<>(); @PostConstruct private void init() { if (!CollectionUtils.isEmpty(handlerList)) { for (Handler handler : handlerList) { handlerMap.put(handler.getType(), handler); } } } public Result<Map<String, Object>> query(Long id, TypeEnum typeEnum) { Handler handler = handlerMap.get(typeEnum); if (null == handler) { return Result.returnFailed(ErrorCode.CAN_NOT_HANDLE); } return handler.query(id); } }

类静态变量、实体变量应定义在类的顶部。类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

► 水平格式

要有适当的缩进和空格。

► 团队统一

通常,同一个团队的风格尽量保持一致。集团对于 Java 开发进行了非常详细的规范。(可点击下方阅读原文,了解更多内容)

类与函数

► 类和函数应短小,更短小

类和函数都不应该过长(集团要求函数长度最多不能超过 80 行),过长的函数可读性一定差,往往也包含了大量重复的代码。

► 函数只做一件事(同一层次的事)

同一个函数的每条执行语句应该是统一层次的抽象。例如,我们经常会写一个函数需要给某个 DTO 赋值,然后再调用接口,接着返回结果。那么这个函数应该包含三步:DTO 赋值,调用接口,处理结果。如果函数中还包含了 DTO 赋值的具体操作,那么说明此函数的执行语句并不是在同一层次的抽象。

► 参数越少越好

参数越多的函数,调用时越麻烦。尽量保持参数数量足够少,最好是没有。

注释

► 别给糟糕的代码加注释,重构他

注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要,往往这样做之后注释就多余了。

► 好的注释提供信息、表达意图、阐释、警告

我们经常遇到这样的情况:注释写的代码执行逻辑与实际代码的逻辑并不符合。大多数时候都是因为代码变化了,而注释并没有跟进变化。所以,注释最好提供一些代码没有的额外信息,展示自己的设计意图,而不是写具体如何实现。

► 删除掉注释的代码

git等版本控制已经帮我们记录了代码的变更历史,没必要继续留着过时的代码,注释的代码也会对阅读等造成干扰。

错误处理

► 错误处理很重要,但他不能搞乱代码逻辑

错误处理应该集中在同一层处理,并且错误处理的函数最好不包含其他的业务逻辑代码,只需要处理错误信息即可。

► 抛出异常时提供足够多的环境和说明,方便排查问题

异常抛出时最好将执行的类名,关键数据,环境信息等均抛出,此时自定义的异常类就派上用场了,通过统一的一层处理异常,可以方便快速地定位到问题。

► 特例模型可消除异常控制或者 null 判断

大多数的异常都是来源于NPE,有时候这个可以通过 Null Object 来消除掉。

► 尽量不要返回 null ,不要传 null 参数

不返回 null 和不传 null 也是为了尽量降低 NPE 的可能性。

如何判断不是好的代码

讨论了好代码的必要条件,我们再来看看好代码的否定条件:什么不是好的代码。Kent Beck 使用味道来形容重构的时机,我认为当代码有坏味道的时候,也代表了其并不是好的代码。

代码的坏味道

► 重复

重复可能是软件中一切邪恶的根源。—— Robert C.Martin

Martin Fowler 也认为坏味道中首当其冲的就是重复代码。

很多时候,当我们消除了重复代码之后,发现代码就已经比原来整洁多了。

► 函数过长、类过大、参数过长

过长的函数解释能力、共享能力、选择能力都较差,也不易维护。

过大的类代表了类做了很多事情,也常常有过多的重复代码。

参数过长,不易理解,调用时也容易出错。

► 发散式变化、霰弹式修改、依恋情结

如果一个类不是单一职责的,则不同的变化可能都需要修改这个类,说明存在发散式变化,应考虑将不同的变化分离开。

如果某个变化需要修改多个类的方法,则说明存在霰弹式修改,应考虑将这些需要修改的方法放入同一个类。

如果函数对于某个类的兴趣高于了自己所处的类,说明存在依恋情结,应考虑将函数转移到他应有的类中。

► 数据泥团

有时候会发现三四个相同的字段,在多个类和函数中均出现,这时候说明有必要给这一组字段建立一个类,将其封装起来。

► 过多的 if...else 或者使用 switch

过多的 if...else 或者 switch ,都应该考虑用多态来替换掉。甚至有些人认为除个别情况外,代码中就不应该存在 if...else 。

总结

本文首先一句话概括了我认为的好代码的必要条件:整洁,接着具体分析了整洁代码的特点,又分析了好代码的否定条件:什么样的代码不是好的代码。仅是本人的一些见解,希望对各位以后的编程有些许的帮助。

我认为仅仅编写出可运行的代码是远远不够的,还要时刻注意代码的整洁度,留下一些漂亮的代码,希望写的代码都能保留并运行 102 年!

后续增加一些实际的例子来说明好的和坏的代码;分享下如何编写整洁代码——自己认为有用的一些编程技巧。

本文转载自互联网,如有侵权,联系删除