软件开发目录规范 ATM框架构建


软件开发的目录规范

建立文件夹

为了提高程序的可读性与可维护性,我们应该为软件设计良好的目录结构,这与规范的编码风格同等重要。软件的目录规范并无硬性标准,只要清晰可读即可

以ATM购物车项目为例:

首先需要建立一个项目文件夹,例如项目名称ATM,这个ATM的大文件夹下需要有几个子文件夹:

bin文件夹:用于存放指令文件,可执行指令,例如:start.py

conf文件夹:配置性的文件,存放一些变量,运行软件时需要从中读取一些变量值,例如:settings.py

db文件夹:放数据库相关的操作代码,例如:db_handle.py

core文件夹:核心代码,例如:src.py

log文件夹:存放日志,查看软件运行时记录的一些日志数据,例如:access.log

lib文件夹:库目录,

db文件夹:存放数据

readme.md:类似于软件说明书

搭框架

在core文件夹下建立src.py文件

==============================在 ATM/core/src.py下================================
def login():
    print('登录功能')  # 简易的功能

def register():
    print('注册功能')

def transfer():
    print('转账功能')



function_dic = {
    '1':['登录',login],
    '2':['注册',register],
    '3':['转账',transfer]
}


def run():  # src文件只负责功能性的定义,不负责程序的运行,到时候调用放在start.py文件里面
  while True:
      print('0 ?? 退出')
      for k in function_dic:
          print('%s ?? %s' %(k,function_dic[k][0]))
      choice = input('输入功能指令>>>')
      if choice == '0':
          break
      if choice in function_dic:
          function_dic[choice][1]()
      else:
          print('指令不存在,重新键入')

在bin文件夹下建立start.py文件

==============================在 ATM/bin/start.py下================================
# 为了软件便于管理维护我们将软件进行拆分
# 让 start.py 能够跨文件调的到 src.py 里面的函数 run,那我们就需要将src文件的文件夹路径加入环境变量
import sys

sys.path.append(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM/core')  # 将src文件的文件夹路径加入环境变量

import src  # 这样就能正常跨文件调用src的功能了

if __name__ == '__main__': # 当这个文件作为主文件运行时就执行里面的子代码 在执行文件里面加上这个更规范一点
    src.run()

但是上述代码仍是有问题的因为我们可能会导入到的文件很多,那我们每次导入都需要添加到环境变量,这样就比较麻烦,我们改进一下:

==============================在 ATM/bin/start.py下================================
import sys

sys.path.append(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM')  # 将文件的软件根目录直接加入环境变量 这样就能用到哪个直接导入

from core import src
if __name__ == '__main__':
    src.run()

这样我们又碰到另一个问题,我们写的是一个软件,当用户将这个软件安装到其它电脑的时候,文件路径和我们写的时候不一样,那这样软件就无法运行。我们需要再改动一下,将文件路径写活了,让它在别的电脑也能运行:

==============================在 ATM/bin/start.py下================================
import sys

import os  # 导入os模块 调用__file__功能,它能够直接动态获取到当前文件的绝对路径

BASE_DIR = os.path.dirname(os.path.dirname(__file__))  # 获取但前文件文件夹的文件夹,也就是副副文件夹,

sys.path.append(BASE_DIR)   # 这样就能够动态获取

from core import src
if __name__ == '__main__':
    src.run()

但还有更省事的办法:

==============================在 ATM/start.py下================================
# 直接将start.py文件移动到项目的根目录下面,因为当前执行文件的文件夹会自动添加到环境变量中,这样我们就不用手动添加路径了
from core import src  # 直接就能导入
if __name__ == '__main__':
    src.run()

接下来我们有一个需求,日志需求,我们需要记录日志,日志功能不是用户需要用到的,他是一个其他功能都可能需要调用到的功能,我们将它放在lib文件夹下,创建文件common.py:

==============================在 ATM/lib/common.py下================================
import time
def logger(msg):
    with open(r'/Users/jessewu/PycharmProjects/pythonProject/test01/study miscellaneous/规范目录/ATM/log/access.log',\
            mode='at',encoding='utf8')as f:
        f.write('%s  %s\n' %(time.strftime('%Y-%m-%d %H:%M:%s'),msg))
        # 简易的日志功能
==============================在 ATM/core/src.py下================================
from lib.common import logger  # 需要调用到lib下common里面的logger功能,需要导入一下,需要注意的是这里的.是路径分隔符的意思

def login():
    print('登录功能')
    logger('登陆了')


def register():
    print('注册功能')
    logger('有人注册了')


def transfer():
    print('转账功能')
    logger('转出了一笔钱')


function_dic = {
    '1': ['登录', login],
    '2': ['注册', register],
    '3': ['转账', transfer]
}
def run():
    while True:
        print('0 ?? 退出')
        for k in function_dic:
            print('%s ?? %s' % (k, function_dic[k][0]))
        choice = input('输入功能指令>>>')
        if choice == '0':
            logger('下线了')
            break
        elif choice in function_dic:
            function_dic[choice][1]()
        else:
            print('指令不存在,重新键入')

虽然日志功能能正常记录了,但是还是老问题,记录日志的文件路径被我们写死了,需要将路径写活,且这个路径能够让用户能够配置的,在conf文件夹下创建文件setting.py:

==============================在 ATM/conf/settings.py下================================
# 需要定义一个路径变量,让它在common模块里面能够被调用到
import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
LOG_PATH = '%s/log/access.log' % BASE_DIR  # 保证用户即自己能改,也可以不改就能用
==============================在 ATM/lib/common.py下================================import timefrom conf import settings  # 调用settings里面的logger功能def logger(msg):    with open('%s' %ssettings.LOG_PATH,mode='at',encoding='utf8')as f:        f.write('%s  %s\n' %(time.strftime('%Y-%m-%d %H:%M:%s'),msg))

升级需求,将写好的logger替换成系统里的logging模块,首先我们在settings.py里面定义好LOGGING_DIC,规定好日志的格式和打印形式,过滤级别,logger表示级别为每条日志打上标签,filters表示过滤,handlers表示将日志如何处理,是打印还是输出到终端:

==============================在 ATM/conf/settings.py下================================# 1、定义三种日志输出格式,日志中可能用到的格式化串如下# %(name)s Logger的名字# %(levelno)s 数字形式的日志级别# %(levelname)s 文本形式的日志级别# %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有# %(filename)s 调用日志输出函数的模块的文件名# %(module)s 调用日志输出函数的模块名# %(funcName)s 调用日志输出函数的函数名# %(lineno)d 调用日志输出函数的语句所在的代码行# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒# %(thread)d 线程ID。可能没有# %(threadName)s 线程名。可能没有# %(process)d 进程ID。可能没有# %(message)s用户输出的消息# 2、强调:其中的%(name)s为getlogger时指定的名字standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \                  '[%(levelname)s][%(message)s]'simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'test_format = '%(asctime)s] %(message)s'LOGGING_DIC = {    'version': 1,    'disable_existing_loggers': False,    'formatters': {        'standard': {            'format': standard_format        },        'simple': {            'format': simple_format        },        'test': {            'format': test_format        },    },    'filters': {},    'handlers': {        #打印到终端的日志        'console': {            'level': 'DEBUG',            'class': 'logging.StreamHandler',  # 打印到屏幕            'formatter': 'simple'        },        #打印到文件的日志,收集info及以上的日志        'default': {            'level': 'DEBUG',            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转            'formatter': 'standard',            # 可以定制日志文件路径            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录            # LOG_PATH = os.path.join(BASE_DIR,'a1.log')            'filename': LOG_PATH,  # 日志文件            'maxBytes': 1024*1024*5,  # 日志大小 5M            'backupCount': 5,            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了        },        'other': {            'level': 'DEBUG',            'class': 'logging.FileHandler',  # 保存到文件            'formatter': 'test',            'filename': 'a2.log',            'encoding': 'utf-8',        },    },    'loggers': {        #logging.getLogger(__name__)拿到的logger配置        '用户相关日志': {            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕            'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)            'propagate': False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递        },    },}

定义好之后,我们到原本的logger功能模块common.py下面将之前的蹩脚功能注释掉,重新写,定义logger函数:

==============================在 ATM/lib/common.py下================================
from conf import settings
import logging.configlogging.config.dictConfig(settings.LOGGING_DIC)  # 固定用法def logger(log_name):    logger1 = logging.getLogger(log_name)  # log_name 使用者需要自己传日志类型名称进去    return logger1
==============================在 ATM/core/src.py下================================
import loggingfrom lib.common import loggerdef login():    print('登录功能')    log = logger('用户相关日志')  # 赋值给变量    log.debug('xxx登录了')  # 调用级别def register():    print('注册功能')    log = logger('用户相关日志')    log.info('有人注册了')def transfer():    print('转账功能')    log = logger('用户相关日志')    log.info('转出了一笔钱')def withdraw():    print('提现功能')    log = logger('用户相关日志')    log.warning('提现金额过大,可能要跑')function_dic = {    '1': ['登录', login],    '2': ['注册', register],    '3': ['转账', transfer],    '4': ['提现', withdraw]}def run():    while True:        print('0 ?? 退出')        for k in function_dic:            print('%s ?? %s' % (k, function_dic[k][0]))        choice = input('输入功能指令>>>')        if choice == '0':            log = logger('用户相关日志')            log.info('下线了')            break        elif choice in function_dic:            function_dic[choice][1]()        else:            print('指令不存在,重新键入')