معرفی توابع تعریفشده توسط کاربر (User-defined Functions) در DAX
توابع تعریفشده توسط کاربر (User-defined functions) در DAX یک قابلیت جدید و هیجانانگیز هستند. در این مقاله، به مفاهیم کلیدی که باید پیش از استفاده از آنها در پروژههای Power BI بدانید، میپردازیم.
اگرچه DAX یک زبان تابعی (Functional Language) است، اما تا پیش از این امکان تعریف توابع دلخواه توسط کاربر را ارائه نمیداد. از نسخه سپتامبر 2025 به بعد، قابلیت تعریف توابع در دسترس قرار گرفته است. این توابع در واقع عبارات پارامتری هستند که میتوان آنها را در کل مدل معنایی (Semantic Model) مجدداً استفاده کرد. در این مقاله نحوه کار این توابع توضیح داده میشود.
توابع در DAX میتوانند برای اشتراکگذاری منطق تجاری (Business Logic) درون یک مدل معنایی و همچنین بین مدلهای مختلف به کار روند.
سادهترین راه برای یادگیری توابع، تعریف آنها در یک Query و بررسی فوری نتایج است. در ادامه، اولین مثال ارائه شده است:
DEFINE
FUNCTION SumTwoNumbers = ( A, B ) => A + B
EVALUATE
{ SumTwoNumbers ( 10, 20 ) }
تعریف یک تابع در DAX شامل دو بخش است:
امضا (Signature): که شامل نام تابع و پارامترها (مثلاً A, B) میشود.
بدنه تابع (Function Body): که عملیات اصلی را مشخص میکند (مثلاً
A + B
).
این دو بخش توسط نماد =>
از هم جدا میشوند. برای فراخوانی تابع نیز مانند سایر توابع داخلی DAX، کافی است نام تابع را همراه با پارامترهایش صدا بزنید.
برای جلوگیری از ایجاد ابهام، همیشه در توابع تعریفشده توسط کاربر از Pascal Case استفاده میکنیم. این روش باعث میشود توابع تعریفشده توسط کاربر (User-defined Functions) که با Pascal Case نوشته میشوند، از توابع از پیشتعریفشده DAX که همیشه با حروف بزرگ (UPPERCASE) هستند، متمایز شوند.
هنگام تعریف پارامترها، میتوان نوع (Type)، زیرنوع (Subtype) و حالت ارسال پارامتر (Parameter-passing mode) را مشخص کرد. مهمترین بخش در این میان، حالت ارسال پارامتر است. در ادامهی فصل، بخشی جداگانه به این موضوع اختصاص داده شده است.
دو حالت اصلی برای ارسال پارامتر وجود دارد:
VAL (Value): مخفف Value. این حالت نشان میدهد که پارامتر پیش از فراخوانی تابع، در زمینه ارزیابی (Evaluation Context) فراخواننده محاسبه میشود. در طول اجرای بدنه تابع، یک مقدار مشخص و ثابت دارد. به همین دلیل، چندین بار استفاده از همان پارامتر همیشه نتیجهی یکسانی خواهد داشت.
EXPR (Expression): مخفف Expression. در این حالت، پارامتر یک عبارت است که در همان زمینهای که در بدنه تابع استفاده میشود، ارزیابی خواهد شد. بنابراین، چندین بار استفاده از یک پارامتر EXPR میتواند (و اغلب اینطور است) نتایج متفاوتی تولید کند.
برای تعریف حالت ارسال پارامتر، از علامت دونقطه (:
) استفاده میکنیم. به عنوان مثال:
FUNCTION SumTwoNumbers = ( A : Val, B : Expr ) => A + B
توسعهدهندگان در DAX همچنین میتوانند نوع پارامترها (Parameter Type) را مشخص کنند. برای این کار، طیف گستردهای از گزینهها وجود دارد که در جدول زیر فهرست شدهاند.
Type | Subtype | Passing mode | Remarks |
ANYVAL | VAL | No restriction, any expression can be used. This is the default. | |
SCALAR | VAL / EXPR | Any scalar value, like an integer or a string; tables cannot be used | |
VARIANT | VAL / EXPR | Scalar expression, any data type is fine. | |
INT64 | VAL / EXPR | Scalar expression, the data type must be Integer. | |
DECIMAL | VAL / EXPR | Scalar expression, the data type must be Fixed Decimal. | |
DOUBLE | VAL / EXPR | Scalar expression, the data type must be Decimal. | |
STRING | VAL / EXPR | Scalar expression, the data type must be String. | |
DATETIME | VAL / EXPR | Scalar expression, the data type must be Date or DateTime. | |
BOOLEAN | VAL / EXPR | Scalar expression, the data type must be Boolean. | |
NUMERIC | VAL / EXPR | Scalar expression, the data type must be Integer. | |
TABLE | VAL / EXPR | Any table expression, scalars are not allowed. | |
ANYREF | EXPR | A reference to any table, column, measure, or calendar. |
نوع پارامتر (Parameter Type) را میتوان همراه با حالت ارسال پارامتر (Parameter-passing mode) مشخص کرد. به عنوان مثال در کد زیر:
FUNCTION SumTwoNumbers = (
a : SCALAR VAL,
b : SCALAR EXPR
) => a + b
توابع در DAX از Casting خودکار پارامترها استفاده میکنند. این بدان معناست که عبارات مورد استفاده برای مقداردهی پارامترها بهطور خودکار به نوع داده موردنیاز تبدیل (Cast) میشوند. این فرآیند میتواند در برخی موارد باعث ایجاد ابهام شود و نیاز به توضیح بیشتری دارد.
برای مثال، تابعی را در نظر بگیرید که انتظار دارد نوع داده پارامتر آن عدد صحیح (Integer) باشد. زمانی که این تابع با مقادیر صحیح فراخوانی شود، همهچیز همانطور که انتظار داریم عمل میکند. به عنوان نمونه، اگر مقادیر ۳ و ۲ به تابع ارسال شوند، خروجی صحیح یعنی ۵ تولید خواهد شد:
DEFINE
FUNCTION SumTwoNumbers = (
a : INT64 VAL,
b : INT64 VAL
) => a + b
EVALUATE
{
SumTwoNumbers( 3, 2 )
}
با این حال، اگر تابع با دو عدد اعشاری (Decimal Numbers) فراخوانی شود، خطایی رخ نخواهد داد. دلیل این موضوع، Casting خودکار است. در واقع، هر آرگومان پیش از اجرای تابع، بهطور خودکار به نوع داده موردنیاز تبدیل میشود.
DEFINE
FUNCTION SumTwoNumbers = (
a : INT64 VAL,
b : INT64 VAL
) => a + b
EVALUATE
{
SumTwoNumbers( 3.4, 2.4 ), -- evaluates INT ( 3.4 ) + INT ( 2.4 )
SumTwoNumbers( "3.4", "2.4" ) -- evaluates INT ( "3.4" ) + INT ( "2.4" )
}
اگرچه حاصل جمع دو عدد اعشاری باید ۵.۸ باشد (که در صورت گرد کردن به عدد صحیح برابر با ۶ خواهد شد)، اما به این دلیل که هر پارامتر بهصورت جداگانه به عدد صحیح (Integer) تبدیل میشود، نتیجهی محاسبه برابر با ۳ + ۲ = ۵ خواهد بود.
درک حالتهای ارسال پارامتر (Parameter-passing modes)
حالت ارسال پارامتر را میتوان با استفاده از علامت دونقطه (:
) به پارامترها اضافه کرد. برای نمونه:
DEFINE
FUNCTION SumTwoNumbers = ( a : VAL, b : VAL ) => a + b
EVALUATE
{ SumTwoNumbers( 10, 20 ) }
در تابعی که در بالا تعریف شد، هر دو پارامتر a و b از نوع Value Parameter هستند. به طور پیشفرض، تمام آرگومانهای توابع در DAX به صورت Value Parameter (VAL) در نظر گرفته میشوند. با این حال، میتوان با استفاده از EXPR آنها را مجبور کرد که بهعنوان Expression Parameter در نظر گرفته شوند.
درک این تفاوت بسیار مهم است؛ زیرا انتخاب اشتباه حالت ارسال پارامتر (Parameter-passing mode) بهراحتی میتواند منجر به بروز خطاها و اشکالاتی شود که رفع آنها دشوار است.
در حالت Value Parameter (VAL):
پارامترها پیش از اجرای تابع توسط Caller (تابعی که فراخوانی را انجام میدهد) ارزیابی میشوند. تابع، مقدار پارامتر را بهعنوان آرگومان دریافت کرده و از آن در بدنه خود استفاده میکند. یک Value Parameter شبیه به یک متغیر است: فرمولی که توسط Caller اجرا میشود، یک مقدار تولید میکند؛ سپس آن مقدار به متغیر تخصیص داده شده و در نهایت تابع اجرا میشود.
به عنوان مثال:
FUNCTION SumTwoNumbers = ( a : VAL, b : VAL ) => a + b
از آنجایی که هر دو پارامتر به صورت VAL تعریف شدهاند، کد به شکل زیر تغییر میکند:
--
-- When the function is invoked with two arguments like this:
--
SumTwoNumbers(
SUM ( Sales[Net Price] ),
SUM ( Sales[Quantity] )
)
--
-- The code being executed is equivalent to this:
--
VAR a = SUM ( Sales[Net Price] )
VAR b = SUM ( Sales[Quantity] )
RETURN
a + b
از سوی دیگر، پارامترهای Expression (با حالت EXPR) توسط Caller (فراخواننده) ارزیابی نمیشوند. این نوع پارامترها بهصورت فرمول به تابع ارسال میشوند و هر بار که تابع از آنها استفاده کند، دوباره ارزیابی خواهند شد.
یک پارامتر EXPR میتواند در بخشهای مختلف بدنه تابع، بسته به Evaluation Context (زمینه ارزیابی) فعال در لحظه اجرا، مقادیر متفاوتی داشته باشد.
در ادامه همان تابع SumTwoNumbers که قبلاً استفاده کردیم را این بار با پارامترهای EXPR مشاهده میکنید:
FUNCTION SumTwoNumbers = ( a : EXPR, b : EXPR ) => a + b
از آنجایی که هر دو پارامتر به صورت EXPR تعریف شدهاند، کد معادل شکل زیر خواهد بود:
--
-- When the function is invoked with two arguments like this:
--
SumTwoNumbers(
SUM ( Sales[Net Price] ),
SUM ( Sales[Quantity] )
)
--
-- The code being executed is equivalent to this:
--
SUM ( Sales[Net Price] ) + SUM ( Sales[Quantity] )
در مثالی که تاکنون بررسی کردیم، تفاوتی در نتیجه تابع مشاهده نمیشود، چه از پارامترهای VAL استفاده شود و چه از EXPR. با این حال، در بیشتر مواقع انتخاب صحیح حالت ارسال پارامتر به معنای تابع (Function’s Semantics) بستگی دارد و میتواند تأثیر بسیار زیادی بر رفتار تابع بگذارد.
استفاده از VAL به صورت پیشفرض است و بهتر است همیشه زمانی از آن استفاده کنید (چه بهصورت ضمنی و چه صریح، که البته توصیه میشود همیشه بهطور صریح مشخص شود) که تابع انتظار دریافت یک مقدار مشخص برای پردازش دارد.
از EXPR باید زمانی استفاده کرد که تابع نیاز دارد یک عبارت (Expression) را در یک Evaluation Context متفاوت ارزیابی کند و بر اساس منطق تابع، نتایج مختلفی تولید نماید.
برای درک بهتر، بیایید یک مثال ببینیم:
تابع زیر برای محاسبه یک عبارت طراحی شده که محاسبات را فقط به محصولات قرمز محدود میکند. اما تابع همانطور که انتظار میرود عمل نمیکند، زیرا حالت ارسال پارامتر بهطور پیشفرض VAL است، در حالی که برای عملکرد صحیح تابع باید از EXPR استفاده شود:
FUNCTION ComputeForRed = ( amount ) =>
CALCULATE (
amount,
'Product'[Color] = "Red"
)
فرض کنید تابع را طوری فراخوانی کنیم که مقدار Sales Amount بهعنوان آرگومان amount به آن ارسال شود. در این حالت، کد به یک متغیر تبدیل میشود و در نتیجه تأثیر تغییر در Filter Context که توسط دستور CALCULATE اعمال شده است، از بین میرود:
--
-- When the function is invoked with an argument like this:
--
ComputeForRed( [Sales Amount] )
--
-- The code being executed is equivalent to this:
--
VAR amount = [Sales Amount]
RETURN
CALCULATE (
amount,
'Product'[Color] = "Red"
)
بدنه تابع ComputeForRed پارامتر ورودی را درون دستور CALCULATE ارزیابی میکند، جایی که Filter Context تغییر داده میشود تا رنگ محصول فقط به قرمز (Red) محدود شود.
اما زمانی که پارامتر به صورت VAL تعریف شده باشد، ارزیابی آرگومان پیش از اجرای تابع انجام میگیرد. در قطعه کدی که قبلاً دیدیم، از یک متغیر amount استفاده کردیم تا این رفتار را شبیهسازی کنیم. از آنجا که پارامتر بهعنوان یک مقدار ثابت در نظر گرفته میشود، بدون توجه به تغییرات در Filter Context تغییری نخواهد کرد. بنابراین، حتی اگر بدنه تابع پارامتر را در زمینهای فیلترشده روی محصولات قرمز ارزیابی کند، نتیجه در واقع همان Sales Amount برای تمام رنگهای محصول قابل مشاهده خواهد بود.
برای حل این مشکل کافی است حالت ارسال پارامتر را از VAL به EXPR تغییر دهیم. در این صورت، تابع بهدرستی عمل خواهد کرد:
FUNCTION ComputeForRed = ( amountExpr : EXPR ) =>
CALCULATE (
amountExpr ,
'Product'[Color] = "Red"
)
بیایید موضوع را با یک مثال واقعیتر ادامه دهیم. در اینجا یک تابع جدول (Table Function) ایجاد میکنیم که بهترین مشتریان را بر اساس یک معیار (Metric) بازمیگرداند.
در این سناریو، یک مشتری زمانی بهعنوان بهترین مشتری (Best Customer) در نظر گرفته میشود که مقدار معیار انتخابی برای همان مشتری بزرگتر از مقدار میانگین همان معیار برای تمام مشتریان باشد.
نکته مهم این است که میخواهیم معیار (Metric) بهعنوان یکی از پارامترهای تابع تعریف شود.
در ادامه یک Query آورده شده است که تابع BestCustomers را تعریف کرده و سپس آن را اجرا میکند:
DEFINE
FUNCTION BestCustomers = ( metricExpr : EXPR ) =>
VAR AverageMetric = AVERAGEX ( Customer, metricExpr )
VAR BestCustomersResult =
FILTER (
Customer,
metricExpr > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( [Sales Amount] )
در کدی که پیشتر نوشتیم، چند نکته مهم وجود دارد که باید به آنها توجه کنیم:
پارامتر metricExpr از نوع EXPR است.
این پارامتر هر بار که فراخوانی شود، دوباره ارزیابی میگردد. در بدنه تابع، دو Iterator مختلف به metricExpr ارجاع میدهند؛ بنابراین metricExpr برای هر ردیف در هر Iterator محاسبه خواهد شد.ارزیابی metricExpr درون AVERAGEX
در تعریف متغیرAverageMetric
، پارامتر metricExpr یکبار بهازای هر مشتری ارزیابی میشود. توجه داشته باشید که این ارزیابی در طول یک Iteration روی جدول Customers رخ میدهد، جایی که AVERAGEX یک Row Context ایجاد میکند. در نتیجه، metricExpr در همان Row Context محاسبه میشود.ارزیابی metricExpr درون FILTER
metricExpr دوباره در طول Iteration مربوط به FILTER، هنگام محاسبه BestCustomers، ارزیابی میشود. این بار یک Row Context متفاوت وجود دارد که توسط FILTER ایجاد شده است. بنابراین، metricExpr یکبار دیگر بهازای هر مشتری در Row Context جدید محاسبه خواهد شد.ارزیابی چندباره در Contextهای مختلف
پارامتر metricExpr چندین بار و در زمینههای متفاوت (Evaluation Contexts) ارزیابی میشود و به همین دلیل، نتایج متفاوتی تولید خواهد کرد. این موضوع کاملاً طبیعی و مورد انتظار است؛ چرا که ما دقیقاً به همین رفتار نیاز داریم. در واقع، metricExpr در دو Row Context مجزا (یکی در AVERAGEX و دیگری در FILTER) فراخوانی میشود و باید هر بار فروش مشتری جاری را از طریق Context Transition محاسبه کند.
نتیجه نهایی
این تابع همانطور که انتظار داریم خروجی صحیح تولید میکند. پس از اجرا، نتیجه شامل ۱٬۸۰۷ مشتری است که بخشی از آنها در اسکرینشات زیر نشان داده شدهاند.
تغییر دادن حالت پارامتر از EXPR به VAL باعث میشود نتیجهی تابع خالی باشد. در کوئری زیر، خروجی یک جدول تهی است و تنها تفاوت آن با نسخهی قبلی، حالت ارسال پارامتر (parameter-passing mode) است.
DEFINE
FUNCTION BestCustomers = ( metricVal : VAL ) =>
VAR AverageMetric = AVERAGEX ( Customer, metricVal )
VAR BestCustomersResult =
FILTER (
Customer,
metricVal > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( [Sales Amount] )
علت خروجی خالی این است که یک پارامتر از نوع VAL تنها یک بار، در لحظهی فراخوانی تابع ارزیابی میشود و دیگر هرگز دوباره محاسبه نمیگردد. هر بار که تابع به این پارامتر دسترسی پیدا کند، همان نتیجه ثابت را دریافت میکند؛ بدون توجه به Evaluation Contextی که پارامتر در آن استفاده میشود.
در مثال VAL، معیار [Sales Amount] پیش از آنکه تابع BestCustomers اجرا شود محاسبه میگردد. بنابراین، پارامتر metricVal فقط شامل مقدار کل (Total) فروش برای همه مشتریان است. سپس در زمان اجرای تابع:
AverageMetric
میانگین چندین مقدار یکسان را محاسبه میکند (چون metricVal دیگر دوباره ارزیابی نمیشود، فقط خوانده میشود).در نهایت، شرط موجود در
FILTER
بررسی میکند که آیا یک مقدار بزرگتر از خودش است یا خیر؛ که همیشه FALSE خواهد بود. به همین دلیل هیچ رکوردی بازگردانده نمیشود.
در حالی که در مثال EXPR، هر بار که به metricExpr ارجاع داده میشود، معیار [Sales Amount] دوباره محاسبه میشود، درست مثل اینکه در بدنه تابع مستقیماً مرجع آن قرار داده شده باشد. همین موضوع باعث میشود نتیجه درست و مطابق انتظار تولید گردد.
نکته مهم
استفاده از پارامترهای VAL حالت پیشفرض است، چون برای اغلب سناریوها سادهترین و شهودیترین روش محسوب میشود. اما همانطور که در دو مثال اخیر مشاهده کردیم، در بسیاری از توابع بهتر است بخشی از کد را از طریق پارامترهای EXPR تزریق کنیم تا امکان محاسبه در کانتکستهای مختلف وجود داشته باشد.
هشدار در استفاده از EXPR
هنگام کار با پارامترهای EXPR باید دقت کرد که تابع چگونه از این پارامتر استفاده میکند.
برای نمونه، بیایید کوئری قبلی را تغییر دهیم. این بار همچنان از یک پارامتر EXPR استفاده میکنیم، اما بهجای معیار [Sales Amount] از تابع SUMX در زمان فراخوانی استفاده میکنیم:
DEFINE
FUNCTION BestCustomers = ( metricExpr : EXPR ) =>
VAR AverageMetric = AVERAGEX ( Customer, metricExpr )
VAR BestCustomersResult =
FILTER (
Customer,
metricExpr > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( SUMX ( Sales, Sales[Quantity] * Sales[Net Price] ) )
جالب است بدانید که این کوئری یک جدول خالی برمیگرداند. این بار پیدا کردن دلیل کمی دشوارتر است، زیرا پارامتر با نوع درست تنظیم شده است؛ بنابراین مشکل جای دیگری است. در واقع، مشکل اینجاست که در طول Iterationها Context Transition رخ نمیدهد.
در مثال قبلی با EXPR، آرگومان تابع BestCustomers یک مرجع به یک Measure یعنی [Sales Amount] بود. اما در آخرین مثال EXPR، ما از یک فرمول ساده شامل SUMX استفاده کردیم که در حقیقت همان تعریف Measure مربوط به Sales Amount است.
وقتی از مرجع Measure بهعنوان آرگومان metricExpr استفاده کردیم، هر بار که در بدنه تابع به metricExpr ارجاع داده شد، یک Context Transition ضمنی (Implicit) رخ داد و کد بهدرستی کار کرد.
اما وقتی بهجای Measure از عبارت SUMX استفاده کردیم، این Context Transition رخ نداد؛ چون CALCULATE ضمنی که در اطراف Measure قرار دارد دیگر وجود ندارد.
DEFINE
FUNCTION BestCustomers = ( metricExpr : EXPR ) =>
VAR AverageMetric = AVERAGEX ( Customer, metricExpr )
VAR BestCustomersResult =
FILTER (
Customer,
metricExpr > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( CALCULATE ( SUMX ( Sales, Sales[Quantity] * Sales[Net Price] ) ) )
اضافه کردن CALCULATE خارجی باعث میشود یک Context Transition رخ دهد و در نتیجه کوئری، همانند قبل، ۱٬۸۰۷ مشتری را بازگرداند.
با این حال، هنگام نوشتن توابع باید به این جزئیات دقت ویژهای داشته باشیم. به جای آنکه روش فراخوانی تابع را تغییر دهیم، بهتر است خود کد تابع را طوری اصلاح کنیم که در مواقع لازم، Context Transition بهطور صریح فعال شود.
در مثال ما، کافی است هر بار که پارامتر metricExpr در یک Row Context ارزیابی میشود، از CALCULATE استفاده کنیم تا مشخص شود که به Context Transition نیاز داریم:
DEFINE
FUNCTION BestCustomers = ( metricExpr : EXPR ) =>
VAR AverageMetric = AVERAGEX ( Customer, CALCULATE ( metricExpr ) )
VAR BestCustomersResult =
FILTER (
Customer,
CALCULATE ( metricExpr ) > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( SUMX ( Sales, Sales[Quantity] * Sales[Net Price] ) )
این نسخه از تابع بدون توجه به نوع پارامتری که دریافت میکند بهدرستی کار میکند؛ چه پارامتر یک فرمول باشد و چه یک Measure. هر زمان که نیاز به Context Transition وجود داشته باشد، خود کد تابع با استفاده صریح از CALCULATE آن را اعمال میکند.
این موضوع شاید در ظاهر یک جزئیات کوچک به نظر برسد، اما همین نکته تفاوت میان کدی است که توسط یک تازهکار نوشته شده با کدی که حاصل کار یک حرفهای DAX است. یک متخصص DAX تضمین میکند که تابع حتی در شرایط غیرمنتظره هم درست عمل کند.
نکته پایانی: بهینهسازی کد توابع
هنگام نوشتن توابع، همیشه بهتر است کد را به بهینهترین شکل ممکن بنویسیم. دلیل این موضوع روشن است: توابع معمولاً توسط چندین Measure و حتی توابع دیگر فراخوانی میشوند. بنابراین، نوشتن کد بهینه میتواند تأثیر چشمگیری بر کارایی مدل معنایی (Semantic Model Performance) داشته باشد.
نسخه نهایی این تابع، با ذخیره کردن نتیجهی metricExpr
برای هر مشتری در یک متغیر (Variable)، تعداد دفعات اجرای آن را کاهش میدهد:
DEFINE
FUNCTION BestCustomers = ( metricExpr : EXPR ) =>
VAR CustomersAndMetric =
ADDCOLUMNS (
Customer,
"@Metric", CALCULATE ( metricExpr )
)
VAR AverageMetric = AVERAGEX ( CustomersAndMetric, [@Metric] )
VAR BestCustomersResult =
FILTER (
CustomersAndMetric,
[@Metric] > AverageMetric
)
RETURN
BestCustomersResult
EVALUATE
BestCustomers( SUMX ( Sales, Sales[Quantity] * Sales[Net Price] ) )
جمعبندی
توابع تعریفشده توسط کاربر (User-defined Functions) یک ابزار ارزشمند برای توسعهدهندگان DAX محسوب میشوند. با ایجاد این توابع، توسعهدهندگان میتوانند کد مدل را به بخشهای کوچکتر و قابل مدیریت تقسیم کنند؛ بخشی که امکان تست و رفع خطا را بهصورت مستقل فراهم میسازد. پس از اعتبارسنجی و بهینهسازی کامل، هر تابع به یک بلوک سازنده تبدیل میشود که استحکام کلی پروژه را افزایش میدهد.
هنگام توسعه توابع، توجه به حالتهای ارسال پارامتر (Parameter-passing modes) بسیار ضروری است. بهویژه در مورد پارامترهای EXPR باید بررسی کرد که آیا نیاز است با استفاده از CALCULATE، Context Transition بهطور صریح فعال شود یا خیر.
در آینده نمیتوان مدلی معنایی (Semantic Model) پیچیده را تصور کرد که تعداد زیادی Measure داشته باشد اما فاقد توابع تعریفشده توسط کاربر باشد. بهطور سنتی، مدیریت پیچیدگی محاسبات بر عهده Measureها و Calculation Groupها بوده است؛ اما این رویکرد اغلب باعث کاهش کارایی و کاهش خوانایی کد میشود.
در مقابل، User-defined Functions روشی کارآمد برای انتزاع محاسبات پیچیده ارائه میدهند؛ روشی که هم کد را سادهتر و قابل فهمتر میسازد و هم بدون افت کارایی اجرا میشود.
دیدگاهتان را بنویسید