从一个案例来看技术人员的级别
开发主导的互联网公司,软件公司的具体技术职位其实有很多,从事了一段时间的技术工作后,如何评判一个技术人员是否已经合格,胜任了岗位的要求?开发总是觉得每天都会学习新技术,很牛,那些技术经理都不写代码,迟早被淘汰。30多岁的开发总会觉得,已经工作了这么多年了,混个架构师应该很容易吧?又或者部门里我干的活最多,最复杂,为什么不给我提升级别呢?
不同的公司对于不同的技术岗位要求不尽相同,但大体上是差不多的,今天我就从一个案例的角度来分析一下不同岗位的人来做同一个任务看到的不同的点。
需求:我要每天的给某个特定客户组发送邮件,用于统计一个客户相关费用(提前还款)
码农
码农当然第一优先追求效率,二话不说,撸代码,从以前写的代码里改吧改吧,凑合用上再说。
一个Program.cs里写上Main函数就可以跑了,一个EmailHelper.cs不知道从哪里Copy来的发送Smtp邮件的代码。
以下就是代码,可以先略过,后面会做说明
using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.IO; using System.Linq; using System.Text; using System.Configuration; using System.Threading.Tasks; using System.Net.Mail; using System.Net; namespace PrepaymentCalculate { class Program { static void Main(string[] args) { DateTime date; if(args.Length==3) { date = new DateTime(int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2])); } else { date = DateTime.Now.Date.AddDays(-3);//'2016/06/16' } Console.WriteLine("Running Start on " + DateTime.Now.ToString()); Console.WriteLine("Calculate the abatement on " + date.ToShortDateString()); bool usecrypt = Convert.ToBoolean(ConfigurationManager.AppSettings["usecrypt"] ?? "false"); string connectionString = StringCrypt.StringDecrypt(usecrypt, ConfigurationManager.ConnectionStrings["CoreDataContext"].ConnectionString); string settlementConnectionString = StringCrypt.StringDecrypt(usecrypt, ConfigurationManager.ConnectionStrings["SettlementDataContext"].ConnectionString); List<CalcObject> temp = new List<CalcObject>(); String query = @"SELECT appId, NumInstalment FROM InstalmentObjects WHERE InstalmentType = 200 AND InstalmentValue > 0 AND DateDiff(day, DatePay, @date1) = 0"; List<SqlParameter> p0 = new List<SqlParameter>(); p0.Add(new SqlParameter("@date1", date)); //获取提前还款的AppId列表 CreateCommand(query, p0, settlementConnectionString, (dr) => { Guid appId = (Guid)dr[0]; int currentNum = (int)dr[1]; temp.Add(new CalcObject { appId = (Guid)dr[0], currentNum = (int)dr[1], }); }); /* query = @"SELECT i.AppId AS appId,MIN(NumInstalment) AS N,p.MonthlyFeeRate AS Rate,ia.Principal,ia.Repayments AS C FROM i INNER JOIN pa ON pa.AppId = i.AppId INNER JOIN p ON p.Id = pa.ProductId AND p.MonthlyFeeNper=3 INNER JOIN ia ON ia.Id = i.AppId WHERE 1=1 AND DateDue>=@date1 AND ia.Id in (SELECT DISTINCT AppId FROM app INNER JOIN inst ON app.id = inst.AppId WHERE app.[Status] = 600 AND DatePay>=@date1 AND DatePay<@date2 AND DatePay<DateDue) GROUP BY i.AppId,p.MonthlyFeeRate,ia.Principal,ia.Repayments having min(NumInstalment)>1"; List<SqlParameter> p = new List<SqlParameter>(); p.Add(new SqlParameter("@date1", date)); p.Add(new SqlParameter("@date2", date.AddDays(1))); //获取提前还款的AppId列表 CreateCommand(query, p, connectionString, (dr) => { Guid appId = (Guid)dr[0]; int currentNum = (int)dr[1]; decimal Rate = (decimal)dr[2]; decimal Pricipal = (decimal)dr[3]; int totalNum = (int)dr[4]; temp.Add(new CalcObject { appId = (Guid)dr[0], currentNum = (int)dr[1], Rate = (decimal)dr[2], Pricipal = (decimal)dr[3], totalNum = (int)dr[4] }); }); */ Console.WriteLine("Get the list of the prepayment user:" + temp.Count); List<FileFormat> file = new List<FileFormat>(); foreach(var item in temp) { query = @"SELECT pay.BankAccountName AS name, euser.IdNumber AS idcard, dbank.BankCategoryName AS bankname, pay.BankAccount AS bankaccount, bank.BankCode AS bankcode, 0 AS mount, co.Content AS phone, p.MonthlyFeeRate AS Rate, iao.Principal, iao.Repayments AS C FROM iao INNER JOIN pa ON pa.AppId = iao.id INNER JOIN p ON p.Id = pa.ProductId AND p.MonthlyFeeNper=3 INNER JOIN euser ON euser.Id = iao.UserId INNER JOIN (SELECT TOP 1 * FROM PAO WHERE ApplicationId = @appId order by DateAdded DESC) AS pao ON iao.id=pao.ApplicationId INNER JOIN pay ON pay.Id = pao.PaymentId INNER JOIN us ON us.Id=iao.UserId INNER JOIN co ON co.Id=us.MobileContactId INNER JOIN (SELECT BankName,BankCategoryName FROM DBO WHERE BankCategoryName IS NOT NULL GROUP BY BankName,BankCategoryName) AS dbank ON dbank.BankName = pay.BankName INNER JOIN BIO AS bank ON dbank.BankCategoryName= bank.BankName WHERE iao.Id = @appId AND iao.Status = 600 AND iao.InstalmentChannel in (0, 1)"; List<SqlParameter> p2 = new List<SqlParameter>(); p2.Add(new SqlParameter("@appId", item.appId)); CreateCommand(query, p2, connectionString, (dr) => { FileFormat line =new FileFormat { name = dr["name"].ToString(), idcard = dr["idcard"].ToString(), bankname = dr["bankname"].ToString(), bankaccount = dr["bankaccount"].ToString(), bankcode = dr["bankcode"].ToString(), phone = dr["phone"].ToString(), rate = (decimal)dr["Rate"], pricipal = (decimal) dr["Principal"], totalNum = (int) dr["C"] }; decimal N = item.currentNum;//当前期数 decimal Rate = line.rate;//管理费率 decimal K = 0.075M; //阈值 decimal C = line.totalNum; //总期数 decimal pricipal = line.pricipal; decimal reduceRate = 0; decimal amount = 0; if(N == 3 || N == 2) { reduceRate = (N - 3 * N / C - 1) * Rate; if (reduceRate >= K) { amount = reduceRate * pricipal; } } else if (N>3) { reduceRate = (3 - 3 * N / C) * Rate; if (reduceRate >= K) { amount = reduceRate * pricipal; } } line.mount = amount.ToString("0.##"); if (amount > 0) { file.Add(line); } Console.WriteLine(item.appId + " " + item.currentNum + " " + line.rate + " " + line.totalNum + " " + line.pricipal); Console.WriteLine(line.name + " " + line.idcard + " " + line.bankname + " " + line.bankaccount + " " + line.bankcode + " " + line.mount + " " + line.phone); }); } var filename = String.Format(@"D:\\{0}.txt", date.ToString("yyyyMMdd")); StreamWriter sw = new StreamWriter(filename); sw.WriteLine("name idcard bankname bankaccount bankcode mount phone"); foreach(var line in file) { sw.WriteLine(line.name + " " + line.idcard + " " + line.bankname + " " + line.bankaccount + " " + line.bankcode + " " + line.mount + " " + line.phone); } sw.Flush(); sw.Dispose(); SendEmailFenqi(filename); Console.WriteLine("Running Finnished on " + DateTime.Now.ToString()); //Console.ReadLine(); } private static void CreateCommand(string queryString, List<SqlParameter> parameters, string connectionString, Action<SqlDataReader> action) { using (SqlConnection connection = new SqlConnection( connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(queryString, connection); command.CommandTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["DBTimeout"] ?? "300"); foreach(var param in parameters) { command.Parameters.Add(param); } SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { action(reader); } } } private static bool SendEmailFenqi(String filePath) { Email email = new Email(); var To = ConfigurationManager.AppSettings["EmailList"]; email.mailFrom = Convert.ToString(ConfigurationManager.AppSettings["EmailFrom"] ?? "service01@postfix.fenqi.im"); email.mailPwd = Convert.ToString(ConfigurationManager.AppSettings["EmailPwd"] ?? ""); email.mailSubject = "每日前3后9客户服务费退还清单"; email.mailBody = "详情见附件"; email.isbodyHtml = true; //是否是HTML email.host = "postfix.fenqi.im";//如果是QQ邮箱则:smtp:qq.com,依次类推 email.port = 25; email.mailToArray = To.Split(';');//接收者邮件集合 email.attachmentsPath = new string[] { filePath }; //email.mailCcArray = new string[] { "******@qq.com" };//抄送者邮件集合 if (email.Send()) { Console.WriteLine("<script type='text/javascript'>alert('发送成功!');history.go(-1)</script>");//发送成功则提示返回当前页面; return true; } else { Console.WriteLine("<script type='text/javascript'>alert('发送失败!');history.go(-1)</script>"); return false; } } private static bool RedirectionUrlValidationCallback(string redirectionUrl) { // The default for the validation callback is to reject the URL. bool result = false; Uri redirectionUri = new Uri(redirectionUrl); // Validate the contents of the redirection URL. In this simple validation // callback, the redirection URL is considered valid if it is using HTTPS // to encrypt the authentication credentials. if (redirectionUri.Scheme == "https") { result = true; } return result; } } public class FileFormat { public string name{get;set;} public string idcard{get;set;} public string bankname{get;set;} public string bankaccount{get;set;} public string bankcode{get;set;} public string mount{get;set;} public string phone { get; set; } public decimal rate { get; set; }//管理费率 public int totalNum { get; set; } //总期数 public decimal pricipal { get; set; } } public class CalcObject { public Guid appId { get; set; } public int currentNum { get; set; }//当前期数 } }
1 using System; 2 using System.Net.Mail; 3 using System.Text; 4 5 namespace PrepaymentCalculate 6 { 7 public class Email 8 { 9 /// <summary> 10 /// 发送者 11 /// </summary> 12 public string mailFrom { get; set; } 13 14 /// <summary> 15 /// 收件人 16 /// </summary> 17 public string[] mailToArray { get; set; } 18 19 /// <summary> 20 /// 抄送 21 /// </summary> 22 public string[] mailCcArray { get; set; } 23 24 /// <summary> 25 /// 标题 26 /// </summary> 27 public string mailSubject { get; set; } 28 29 /// <summary> 30 /// 正文 31 /// </summary> 32 public string mailBody { get; set; } 33 34 /// <summary> 35 /// 发件人密码 36 /// </summary> 37 public string mailPwd { get; set; } 38 39 /// <summary> 40 /// SMTP邮件服务器 41 /// </summary> 42 public string host { get; set; } 43 44 /// <summary> 45 /// SMTP邮件服务器端口 46 /// </summary> 47 public int port { get; set; } 48 /// <summary> 49 /// 正文是否是html格式 50 /// </summary> 51 public bool isbodyHtml { get; set; } 52 53 /// <summary> 54 /// 附件 55 /// </summary> 56 public string[] attachmentsPath { get; set; } 57 58 public bool Send() 59 { 60 //使用指定的邮件地址初始化MailAddress实例 61 MailAddress maddr = new MailAddress(mailFrom); 62 //初始化MailMessage实例 63 MailMessage myMail = new MailMessage(); 64 65 66 //向收件人地址集合添加邮件地址 67 if (mailToArray != null) 68 { 69 for (int i = 0; i < mailToArray.Length; i++) 70 { 71 if (!String.IsNullOrEmpty(mailToArray[i])) 72 { 73 myMail.To.Add(mailToArray[i].ToString()); 74 } 75 } 76 } 77 78 //向抄送收件人地址集合添加邮件地址 79 if (mailCcArray != null) 80 { 81 for (int i = 0; i < mailCcArray.Length; i++) 82 { 83 if (!String.IsNullOrEmpty(mailCcArray[i])) 84 { 85 myMail.CC.Add(mailCcArray[i].ToString()); 86 } 87 } 88 } 89 //发件人地址 90 myMail.From = maddr; 91 92 //电子邮件的标题 93 myMail.Subject = mailSubject; 94 95 //电子邮件的主题内容使用的编码 96 myMail.SubjectEncoding = Encoding.UTF8; 97 98 //电子邮件正文 99 myMail.Body = mailBody; 100 101 //电子邮件正文的编码 102 myMail.BodyEncoding = Encoding.Default; 103 104 myMail.Priority = MailPriority.High; 105 106 myMail.IsBodyHtml = isbodyHtml; 107 108 //在有附件的情况下添加附件 109 try 110 { 111 if (attachmentsPath != null && attachmentsPath.Length > 0) 112 { 113 Attachment attachFile = null; 114 foreach (string path in attachmentsPath) 115 { 116 attachFile = new Attachment(path); 117 myMail.Attachments.Add(attachFile); 118 } 119 } 120 } 121 catch (Exception err) 122 { 123 throw new Exception("在添加附件时有错误:" + err); 124 } 125 126 SmtpClient smtp = new SmtpClient(); 127 //指定发件人的邮件地址和密码以验证发件人身份 128 smtp.UseDefaultCredentials = false; 129 smtp.Credentials = new System.Net.NetworkCredential(mailFrom, mailPwd); 130 smtp.DeliveryMethod = SmtpDeliveryMethod.Network; 131 //设置SMTP邮件服务器 132 smtp.Host = host; 133 smtp.Port = port!=0?port:25; 134 try 135 { 136 //将邮件发送到SMTP邮件服务器 137 smtp.Send(myMail); 138 return true; 139 140 } 141 catch 142 { 143 return false; 144 } 145 146 } 147 148 //public bool SendByExchange() 149 //{ 150 // // 实例化ExchageService 151 // ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1); 152 // // 指定用户名,密码,和域名 153 // service.Credentials = new WebCredentials("username", "password", "domain"); 154 // // 指定Exchage服务的url地址 155 // service.Url = new Url("your Exchange Service url Address"); 156 // // 指定邮箱账号 157 // service.AutodiscoverUrl("your e-mail address"); 158 159 // EmailMessage message = new EmailMessage(service); 160 // // 邮件主题 161 // message.Subject = "Hello kevindiao!"; 162 // message.Body = new MessageBody(); 163 // // 指定发送邮件的格式,可以是Text和Html格式 164 // message.Body.BodyType = BodyType.Text; 165 // // 邮件内容 166 // message.Body.Text = "Sent using the EWS Managed API."; 167 // // 可以添加多个邮件人.也可以添加一个集合,用 168 // // message.ToRecipients.AddRange(IList toEmailAddress_list) 169 // message.ToRecipients.Add("test@klx168.com"); 170 // // 保存草稿 171 // //message.save(); 172 // // 只发送不保存邮件 173 // // message.Send(); 174 // // 发送并保存已发送邮件 175 // message.SendAndSaveCopy(); 176 //} 177 } 178 }
(以上代码经生产验证,是可以执行的)
程序员
程序员不同于码农的地方?在很多地方都是一样的。不过程序员的基础会更好些,写代码会更有追求。追求?什么是追求?就好比这个地盘归你管,你至少把家具搬到家里后,打扫整理一下。
- 无用的注释--删除(上段代码的开始标红部分)
- 无效的引用import--清理掉(见上段代码标红的部分)
- 代码的缩进--整理一下(参见下段代码)
- try-catch加一下(参见下段代码)
- 日志输出一下(参见日志输出标红部分)
- 程序执行完成后知道告知结果(参见下段代码)
using System; using System.Collections.Generic; using System.Configuration; using System.Data.SqlClient; using System.IO; namespace PrepaymentCalculate { class Program { static void Main(string[] args) { try { DateTime date; if (args.Length == 3) { date = new DateTime(int.Parse(args[0]), int.Parse(args[1]), int.Parse(args[2])); } else { date = DateTime.Now.Date.AddDays(-3);//'2016/06/16' } Console.WriteLine("Running Start on " + DateTime.Now.ToString()); Console.WriteLine("Calculate the abatement on " + date.ToShortDateString()); bool usecrypt = Convert.ToBoolean(ConfigurationManager.AppSettings["usecrypt"] ?? "false"); string connectionString = StringCrypt.StringDecrypt(usecrypt, ConfigurationManager.ConnectionStrings["CoreDataContext"].ConnectionString); string settlementConnectionString = StringCrypt.StringDecrypt(usecrypt, ConfigurationManager.ConnectionStrings["SettlementDataContext"].ConnectionString); List<CalcObject> temp = new List<CalcObject>(); String query = @"SELECT appId, NumInstalment FROM IO WHERE InstalmentType = 200 AND InstalmentValue > 0 AND DateDiff(day, DatePay, @date1) = 0"; List<SqlParameter> p0 = new List<SqlParameter>(); p0.Add(new SqlParameter("@date1", date)); //获取提前还款的AppId列表 CreateCommand(query, p0, settlementConnectionString, (dr) => { Guid appId = (Guid)dr[0]; int currentNum = (int)dr[1]; temp.Add(new CalcObject { appId = (Guid)dr[0], currentNum = (int)dr[1], }); }); Console.WriteLine("Get the list of the prepayment user:" + temp.Count); List<FileFormat> file = new List<FileFormat>(); foreach (var item in temp) { query = @"SELECT pay.BankAccountName AS name, euser.IdNumber AS idcard, dbank.BankCategoryName AS bankname, pay.BankAccount AS bankaccount, bank.BankCode AS bankcode, 0 AS mount, co.Content AS phone, p.MonthlyFeeRate AS Rate, iao.Principal, iao.Repayments AS C FROM iao INNER JOIN pa ON pa.AppId = iao.id INNER JOIN p ON p.Id = pa.ProductId AND p.MonthlyFeeNper=3 INNER JOIN euser ON euser.Id = iao.UserId INNER JOIN (SELECT TOP 1 * FROM PAO WHERE ApplicationId = @appId order by DateAdded DESC) AS pao ON iao.id=pao.ApplicationId INNER JOIN pay ON pay.Id = pao.PaymentId INNER JOIN us ON us.Id=iao.UserId INNER JOIN co ON co.Id=us.MobileContactId INNER JOIN (SELECT BankName,BankCategoryName FROM DBO WHERE BankCategoryName IS NOT NULL GROUP BY BankName,BankCategoryName) AS dbank ON dbank.BankName = pay.BankName INNER JOIN bank ON dbank.BankCategoryName= bank.BankName WHERE iao.Id = @appId AND iao.Status = 600 AND iao.InstalmentChannel in (0, 1)"; List<SqlParameter> p2 = new List<SqlParameter>(); p2.Add(new SqlParameter("@appId", item.appId)); CreateCommand(query, p2, connectionString, (dr) => { FileFormat line = new FileFormat { name = dr["name"].ToString(), idcard = dr["idcard"].ToString(), bankname = dr["bankname"].ToString(), bankaccount = dr["bankaccount"].ToString(), bankcode = dr["bankcode"].ToString(), phone = dr["phone"].ToString(), rate = (decimal)dr["Rate"], pricipal = (decimal)dr["Principal"], totalNum = (int)dr["C"] }; decimal N = item.currentNum;//当前期数 decimal Rate = line.rate;//管理费率 decimal K = 0.075M; //阈值 decimal C = line.totalNum; //总期数 decimal pricipal = line.pricipal; decimal reduceRate = 0; decimal amount = 0; if (N == 3 || N == 2) { reduceRate = (N - 3 * N / C - 1) * Rate; if (reduceRate >= K) { amount = reduceRate * pricipal; } } else if (N > 3) { reduceRate = (3 - 3 * N / C) * Rate; if (reduceRate >= K) { amount = reduceRate * pricipal; } } line.mount = amount.ToString("0.##"); if (amount > 0) { file.Add(line); } Console.WriteLine(item.appId + " " + item.currentNum + " " + line.rate + " " + line.totalNum + " " + line.pricipal); Console.WriteLine(line.name + " " + line.idcard + " " + line.bankname + " " + line.bankaccount + " " + line.bankcode + " " + line.mount + " " + line.phone); }); } var filename = String.Format(@"D:\\{0}.txt", date.ToString("yyyyMMdd")); StreamWriter sw = new StreamWriter(filename); sw.WriteLine("name idcard bankname bankaccount bankcode mount phone"); foreach (var line in file) { sw.WriteLine(line.name + " " + line.idcard + " " + line.bankname + " " + line.bankaccount + " " + line.bankcode + " " + line.mount + " " + line.phone); } sw.Flush(); sw.Dispose(); SendEmailFenqi(filename); Console.WriteLine("Running Finnished on " + DateTime.Now.ToString()); //最终完成的时间 } catch(Exception ex) { Console.WriteLine("Error in processing... The Exception is " + ex.StackTrace); } } private static void CreateCommand(string queryString, List<SqlParameter> parameters, string connectionString, Action<SqlDataReader> action) { using (SqlConnection connection = new SqlConnection( connectionString)) { connection.Open(); SqlCommand command = new SqlCommand(queryString, connection); command.CommandTimeout = Convert.ToInt32(ConfigurationManager.AppSettings["DBTimeout"] ?? "300"); foreach (var param in parameters) { command.Parameters.Add(param); } SqlDataReader reader = command.ExecuteReader(); while (reader.Read()) { action(reader); } } } private static bool SendEmailFenqi(String filePath) { Email email = new Email(); var To = ConfigurationManager.AppSettings["EmailList"]; email.mailFrom = Convert.ToString(ConfigurationManager.AppSettings["EmailFrom"] ?? "service01@postfix.fenqi.im"); email.mailPwd = Convert.ToString(ConfigurationManager.AppSettings["EmailPwd"] ?? ""); email.mailSubject = "每日前3后9客户服务费退还清单"; email.mailBody = "详情见附件"; email.isbodyHtml = true; //是否是HTML email.host = "postfix.fenqi.im";//如果是QQ邮箱则:smtp:qq.com,依次类推 email.port = 25; email.mailToArray = To.Split(';');//接收者邮件集合 email.attachmentsPath = new string[] { filePath }; if (email.Send()) { Console.WriteLine("<script type='text/javascript'>alert('发送成功!');history.go(-1)</script>");//发送成功则提示返回当前页面; return true; } else { Console.WriteLine("<script type='text/javascript'>alert('发送失败!');history.go(-1)</script>"); return false; } } } public class FileFormat { public string name { get; set; } public string idcard { get; set; } public string bankname { get; set; } public string bankaccount { get; set; } public string bankcode { get; set; } public string mount { get; set; } public string phone { get; set; } public decimal rate { get; set; }//管理费率 public int totalNum { get; set; } //总期数 public decimal pricipal { get; set; } } public class CalcObject { public Guid appId { get; set; } public int currentNum { get; set; }//当前期数 } }
(以上代码根据前面说的略略改动了一下,去掉了一下一些无用的注释和代码,整理了一下)
另外,什么代码规范,设计规范什么的,根据各家的不同,自行处理。
例如:
将计算部分独立抽取出一个函数来,不放在main中,让代码看起来更清晰。
输入参数的部分,有3个选择,一个是可以手工输入3个数值,一个是默认,格式不对的话,都会取默认。相关的校验需要加上,必须判断是否是正确的时间型。或者考虑更合理的参数传入机制,或者做成配置项等。
还有类似阈值等都可以做成配置项。
高级程序员
高级程序员和低级程序员的区别是什么?写过更多的代码有更好的预见性。
预见性?这个说的有点虚。什么是预见性?前面提到的有一些像阈值也算预见性,可它还有更深的内涵。就拿这个案例来说。
发送的内容有没有可能变更?当然可能变,可以考虑做点模板,然后把模板独立在程序外面。
邮件的接收者是不是会变?会变的话,是3个月变一次,还是3天变一次,或者随时变?三个月变甚至可以直接写在代码里,3天变就搞成配置,随时变就从数据库里读取。
邮件的发送频率会不会变?一个月一次,一天一次,一个小时一次?一个月或者一天看起来没什么问题,但一个小时就有可能有。例如前面读取的数据并进行处理,可能数据量大,一个小时都没有处理完呢?
生产环境,执行者无法看到屏幕上的console输出?将console输出的内容,同样的搞一个日志文件记录。给开发者开放日志记录的权限。
架构师
架构师常常就是高级程序员成长起来的。架构师的层次在于跳出业务层面的束缚,可以看到广阔的世界,就好像是上帝视角。
前面提到的接收者会不会变?那么我们思考一下,假设这个接收者从配置中读出来是一个邮件组:admin@fenqi.im。如果这个邮箱组有10000个人,OK,那么发送10000个邮件的负载会被加到邮件服务器上。那么如果读出来的不是一个邮件组,而是10000个人的单独邮箱,我们需要考虑什么问题?
第一,邮件服务器是不是能承受这么多的发送请求,会不会阻塞?
是否有缓存机制或者有足够的吞吐率?
第二,单个人的邮件是否收到,需不需要记录处理?
如果没有收到,考虑自动补发一下?补发了,如果发生网络波动,会不会造成发送请求的暴增,然后搞挂了邮件服务器?
第三,发送的部分以前的代码里是一个循环,有没有办法改进?
这个程序需不需要搞成分布式的,部署多个?这样就可以很快的处理掉大量发送目标的问题?
很多问题是来自现实情况,有一个很经典的例子是一个每天1000访问量的网站,和一个每秒1000访问量的网站架构能一样吗?架构师就是要未卜先知的预见到各种问题,并提前的给出方案。
技术经理
除了架构师,研发团队还有一个角色是技术经理。技术经理如果碰到这个案例,他最先会干什么?找产品确认清楚需求和需求的价值。这个邮件的需求来源是什么?目标是什么?临时的方案还是长久的方案?计算一下需要投入多少开发,多少测试,做完了之后,客户产生了什么效益?
这个案例的需求是财务组需要统计提前还款相关的数据,接收邮件的人不会超过10个,会有少量变更,但频率不高。计算中的一些阈值等基本不会变。收到邮件后,只是做一个简单的报表的分析,如果发生数据上的少量偏差,不会影响财务核算。如果邮件没有收到,如果有补充的触发机制就可以了。这个需求总体上来说,是一个临时的需求,可能跑几个月试验,之后财务部的提前还款方案有可能会改进,这个数据也有可能会做成报表,直接用web来显示,所以基本上不存在需要高级架构师介入搞一个分布式系统的问题……。
之后技术经理要干的,就是安排一个实习生按照码农的方式,撸了一段代码,就是上面的那段,然后大家都很开心,哈哈。Over。