Error Propagation হলো একটি প্রক্রিয়া যা ত্রুটিকে উৎপত্তি স্থান থেকে কল স্ট্যাকের উপরে হ্যান্ডলার পর্যন্ত ছড়িয়ে দেয়। যখন একটি ফাংশন নিজে থেকে ত্রুটি হ্যান্ডল করতে পারে না, তখন এটি ব্যতিক্রম (exception), throws-ঘোষণা বা Return Type-এর মাধ্যমে কলকারী পক্ষের কাছে তা পাঠায়। মোবাইল অ্যাপ্লিকেশনের স্থিতিশীলতার জন্য propagation-এর সঠিক বাস্তবায়ন অত্যন্ত গুরুত্বপূর্ণ: অনহ্যান্ডল বা ভুলভাবে প্রেরিত ত্রুটিগুলি ক্র্যাশের কারণ হয়। Apple Swift Documentation (2026) অনুসারে, Swift-এ throws-এর মাধ্যমে স্বয়ংক্রিয় propagation boilerplate-কোড ছাড়াই যেকোনো স্তরে ত্রুটি পাঠাতে দেয়।
মূল বিষয়
Error Propagation (ত্রুটি প্রসার) হলো ত্রুটি অবজেক্টটি যেখানে ঘটেছে সেই ফাংশন থেকে কল চেইনের উপরে নিকটতম উপযুক্ত হ্যান্ডলার পর্যন্ত পাঠানোর প্রক্রিয়া। কল স্ট্যাকটি কল্পনা করুন: ViewController ViewModel-কে কল করে, ViewModel Repository-কে কল করে, Repository API-কে কল করে। যদি API একটি নেটওয়ার্ক ত্রুটি ফেরত দেয়, তবে এটি Repository এবং ViewModel-এর মাধ্যমে ViewController-এ যেতে হবে, যা ব্যবহারকারীকে একটি বার্তা দেখাবে। প্রতিটি মধ্যবর্তী ফাংশন সিদ্ধান্ত নেয়: ত্রুটিটি হ্যান্ডল করবে নাকি আরও উপরে পাঠাবে (propagate)।
Propagation-এর দুটি পদ্ধতি রয়েছে: স্বয়ংক্রিয় এবং ম্যানুয়াল। স্বয়ংক্রিয় পদ্ধতিতে (Swift throws, Java checked exceptions) কম্পাইলার ডেভেলপারকে ত্রুটিটি হ্যান্ডল করতে বা সিগনেচারে propagation ঘোষণা করতে বাধ্য করে। ম্যানুয়াল পদ্ধতিতে (Result Type, Kotlin Try) ত্রুটিটি মান হিসাবে পাঠানো হয় — ডেভেলপার স্পষ্টভাবে ত্রুটি পাঠানো বা রূপান্তরের জন্য কোড লেখে। Kotlin Result Docs (2026) অনুসারে, Kotlin-এ Result<T> সরাসরি ফাংশন সীমানা পেরিয়ে propagation-এর জন্য তৈরি নয় — এটি প্রতিটি স্তরে রূপান্তর বা হ্যান্ডল করতে হয়, যা propagation-কে আরও সচেতন কিন্তু আরও শব্দবহুল করে তোলে।
পদ্ধতির পছন্দ অ্যাপ্লিকেশন আর্কিটেকচার এবং ভাষার উপর নির্ভর করে। Swift-এ throws-এর মাধ্যমে স্বয়ংক্রিয় propagation প্রভাবশালী, Kotlin-এ ব্যতিক্রম (অপ্রত্যাশিত ত্রুটির জন্য) এবং Result-সদৃশ কন্টেইনার (প্রত্যাশিত ত্রুটির জন্য) এর মিশ্রণ ব্যবহৃত হয়। এটা বোঝা গুরুত্বপূর্ণ: propagation কোনো লক্ষ্য নয়, বরং প্রয়োজনীয়তা। একটি আদর্শ আর্কিটেকচার propagation-এর গভীরতা কমিয়ে দেয়, সর্বনিম্ন সম্ভাব্য স্তরে ত্রুটি হ্যান্ডল করে যেখানে সিদ্ধান্ত নেওয়ার জন্য যথেষ্ট প্রসঙ্গ থাকে।
Swift-এ, throws-এর মাধ্যমে propagation স্বয়ংক্রিয়: যদি ফাংশন A (throws-সহ) ফাংশন B (throws-সহ) কল করে, এবং A do-catch-এ B-এর ত্রুটি হ্যান্ডল না করে, তাহলে ত্রুটিটি স্বয়ংক্রিয়ভাবে A-এর কলকারীর কাছে চলে যায়। এটি Java checked exceptions-এর সাধারণ boilerplate কোড দূর করে, যেখানে চেইনের প্রতিটি মেথডে throws ঘোষণা করতে হয়। Swift নীতি ব্যবহার করে «চেইনে একটি throws-ফাংশন = পুরো চেইনটি throws হয়ে যায়, যদি মধ্যবর্তী স্তরে হ্যান্ডল না করা হয়»।
struct UserRepository {
func fetchUser(id: Int) throws -> User {
let data = try networkService.request(path: "/users/\(id)")
return try parseUser(from: data)
}
}
class UserViewModel {
let repo = UserRepository()
func loadUser(id: Int) throws -> User {
return try repo.fetchUser(id: id)
}
}
// ViewController — চূড়ান্ত হ্যান্ডলার
func onButtonTap() {
let vm = UserViewModel()
do {
let user = try vm.loadUser(id: 42)
updateUI(user)
} catch {
showError("ব্যবহারকারী লোড করতে ব্যর্থ")
}
}
Propagation চেইন: networkService.request -> fetchUser -> loadUser -> onButtonTap। প্রতিটি মধ্যবর্তী ফাংশন throws দিয়ে চিহ্নিত এবং do-catch ধারণ করে না — ত্রুটিটি স্বয়ংক্রিয়ভাবে উপরে পাঠানো হয়। ViewController onButtonTap do-catch-সহ চূড়ান্ত হ্যান্ডলার। যদি ViewModel ত্রুটিটি রূপান্তর করার সিদ্ধান্ত নেয় (অন্য ধরনে মোড়ানো), তবে এটি do-catch এবং নতুন throw ব্যবহার করতে পারে। স্বয়ংক্রিয় propagation কোড হ্রাস করে: Repository-কে জানার প্রয়োজন নেই কিভাবে ত্রুটি হ্যান্ডল করতে — এটি ViewController-এর দায়িত্ব, যার ব্যবহারকারীকে বার্তা দেখানোর জন্য UI-তে অ্যাক্সেস রয়েছে।
Kotlin-এ, ব্যতিক্রমের মাধ্যমে propagation-এর জন্য সিগনেচারে throws ঘোষণার প্রয়োজন নেই (সমস্ত ব্যতিক্রম unchecked)। ব্যতিক্রমটি স্বয়ংক্রিয়ভাবে স্ট্যাকের উপরে উঠতে থাকে যতক্ষণ না try-catch পাওয়া যায়। তবে, সিগনেচারে throws-এর অনুপস্থিতি propagation-কে অন্তর্নিহিত করে তোলে: ডেভেলপার ফাংশনের সিগনেচার থেকে দেখতে পায় না যে এটি ব্যতিক্রম ফেলতে পারে। এটি একইসাথে প্লাস (কম boilerplate) এবং মাইনাস (হ্যান্ডল করতে ভুলে যাওয়া সহজ)। Kotlin এই সমস্যাটি কনভেনশন এবং আর্কিটেকচারাল প্যাটার্নের মাধ্যমে সমাধান করে, ভাষার মাধ্যমে নয়।
class UserRepository(
private val api: ApiService,
private val db: Database
) {
suspend fun getUser(id: String): User {
return try {
api.fetchUser(id)
} catch (e: IOException) {
db.getCachedUser(id) ?: throw AppException("User unavailable")
}
}
}
class UserViewModel(private val repo: UserRepository) {
private val _state = MutableStateFlow<UiState<User>>(UiState.Loading)
val state: StateFlow<UiState<User>> = _state
fun loadUser(id: String) {
viewModelScope.launch {
try {
val user = repo.getUser(id)
_state.value = UiState.Success(user)
} catch (e: AppException) {
_state.value = UiState.Error(e.message ?: "Unknown")
}
}
}
}
Repository-এ, রূপান্তর-সহ propagation: IOException-এ (নেটওয়ার্ক অনুপলব্ধ) ফাংশন ডাটাবেস থেকে ক্যাশেড ডেটা পাওয়ার চেষ্টা করে। যদি ক্যাশ খালি থাকে, AppException ফেলে — propagation নতুন ত্রুটি ধরনের সাথে চলতে থাকে। ViewModel AppException ধরে এবং UiState.Error-এ অনুবাদ করে — ত্রুটিটি আর এগোয় না, UI স্তরে propagation শেষ হয়। Kotlin Coroutines বৈশিষ্ট্য যোগ করে: launch-এ ব্যতিক্রম স্বয়ংক্রিয়ভাবে CoroutineExceptionHandler-এর মাধ্যমে ছড়ায়, এবং async-এ — শুধুমাত্র await() কল করলে। করুটিনে propagation ডিজাইন করার সময় এটি গুরুত্বপূর্ণ — SupervisorJob চাইল্ড করুটিনে ত্রুটিতে প্যারেন্ট করুটিনের বাতিলকরণ প্রতিরোধ করে।
ব্যতিক্রমের বিকল্প হলো কন্টেইনার টাইপের মাধ্যমে propagation যা সাফল্য বা ত্রুটিকে মান হিসাবে পাঠায়। এই পদ্ধতিতে, ফাংশন মান নয় বরং একটি মোড়ক ফেরত দেয়: Swift-এ Result<T, E>, Kotlin-এ Result<T>, Dart-এ Either<L, R> (fpdart বা dartz প্যাকেজ থেকে)। ত্রুটিটি স্ট্যাক আনওয়াইন্ড করে না — এটি কেবল কন্টেইনারের ভিতরে থাকে, এবং পরবর্তী স্তর সিদ্ধান্ত নেয় কী করতে হবে। এটি propagation-কে আরও স্পষ্ট এবং নিয়ন্ত্রণযোগ্য করে তোলে।
data class HttpResult<out T>(
val data: T?,
val error: AppError?
) {
val isSuccess: Boolean get() = data != null
val isError: Boolean get() = error != null
}
sealed class AppError {
data class Network(val message: String) : AppError()
data class Auth(val message: String) : AppError()
}
fun fetchUser(id: String): HttpResult<User> {
return try {
val response = api.get("/users/$id")
HttpResult(data = parseUser(response), error = null)
} catch (e: IOException) {
HttpResult(data = null, error = AppError.Network("No internet"))
}
}
HttpResult<T> — data এবং error ফিল্ড সহ সরল কন্টেইনার। Sealed class AppError ত্রুটির ধরন (Network, Auth) নির্ধারণ করে। fetchUser ফাংশন HttpResult ফেরত দেয়, propagation-এ স্ট্যাক আনওয়াইন্ডিং প্রয়োজন হয় না — কলকারী কেবল isSuccess/isError পরীক্ষা করে। এই পদ্ধতি বিশেষ করে Clean Architecture-এ উপযোগী, যেখানে প্রতিটি স্তর (data, domain, presentation) ত্রুটি রূপান্তর করতে পারে: IOError -> DomainError -> UiError। কন্টেইনারের মাধ্যমে propagation এই রূপান্তরগুলিকে স্পষ্ট এবং পরীক্ষাযোগ্য করে তোলে, ব্যতিক্রমের বিপরীতে যেখানে রূপান্তর চেইন ফাংশন সিগনেচারে দৃশ্যমান নয়।
ত্রুটি হ্যান্ডলিং ডিজাইনের মূল সিদ্ধান্তগুলির মধ্যে একটি হলো propagation (উপরে পাঠানো) এবং handling (এখানে হ্যান্ডল করা) এর মধ্যে পছন্দ। সিদ্ধান্তের নিয়ম: সেই স্তরে ত্রুটি হ্যান্ডল করুন যেখানে অর্থপূর্ণ কর্মের জন্য যথেষ্ট প্রসঙ্গ আছে। যদি আপনার UI-তে অ্যাক্সেস থাকে — ব্যবহারকারীকে বার্তা দেখান। যদি আপনার ক্যাশে অ্যাক্সেস থাকে — পুনরুদ্ধারের চেষ্টা করুন। যদি কিছুই না থাকে — propagate করুন।
| পরিস্থিতি | কর্ম | যুক্তি |
|---|---|---|
| Repository-তে নেটওয়ার্ক ত্রুটি | Propagate | Repository জানে না ব্যবহারকারী অনুরোধ পুনরায় করতে চায় কিনা |
| Repository-তে পার্সিং ত্রুটি | হ্যান্ডল (ডিফল্ট ফেরত দিন) | Repository ফরম্যাট জানে, ফলব্যাক মান ফেরত দিতে পারে |
| ViewModel-এ টাইমআউট | হ্যান্ডল (UiState.Error) | ViewModel UiState পরিচালনা করে, ত্রুটি অনুবাদ করতে জানে |
| Interceptor-এ অনুমোদন ত্রুটি | হ্যান্ডল (টোকেন রিফ্রেশ) | Interceptor-এর টোকেনে অ্যাক্সেস আছে এবং সেশন পুনরুদ্ধার করতে পারে |
| UseCase-এ অজানা ত্রুটি | Propagate | UseCase-এর UI প্রসঙ্গ নেই — শুধুমাত্র ব্যবসায়িক যুক্তি |
সুবর্ণ নিয়ম: সর্বনিম্ন propagation, নিম্ন স্তরে সর্বোচ্চ handling। যদি Repository ক্যাশ থেকে পুনরুদ্ধার করতে পারে — এটি ত্রুটি উপরে না পাঠিয়ে তা করা উচিত। যদি ViewModel Snackbar দেখাতে পারে — এটি দেখাক, ViewController থেকে অতিরিক্ত কোডের প্রয়োজন ছাড়াই। Propagation-এর প্রতিটি স্তর সংযুক্তি বাড়ায় এবং পরীক্ষা জটিল করে তোলে। Google Android Architecture Guide (2026) অনুসারে, ViewModel স্তরে সমস্ত সম্ভাব্য অবস্থা (Loading, Success, Error) উপস্থাপনের জন্য sealed class UiState ব্যবহার করে এবং সরাসরি UI স্তরে ব্যতিক্রম না পাঠিয়ে স্তর সীমানা পেরিয়ে propagation কমানোর সুপারিশ করা হয়।
ভুল propagation মোবাইল অ্যাপ্লিকেশনে খুঁজে পাওয়া কঠিন বাগের উৎস। আসুন পাঁচটি প্রধান সমস্যা বিবেচনা করি যা ডেভেলপারদের মুখোমুখি হয় এবং সেগুলি সমাধানের উপায়।
সবচেয়ে সাধারণ সমস্যা: propagation-এর সময়, ব্যতিক্রম ধরা হয়, লগ করা হয় এবং মূল ব্যতিক্রম ছাড়াই নতুন ফেলা হয়। ডেভেলপার StackTrace হারায় এবং বুঝতে পারে না ঠিক কোথায় ত্রুটিটি ঘটেছে। Swift-এ, error chaining ব্যবহার করুন: throw MyError(context: originalError)। Kotlin-এ: throw AppException(cause = originalException)। Dart-এ: throw AppException(message, originalException)। cause/underlyingError না পাঠিয়ে কখনও নতুন ব্যতিক্রম তৈরি করবেন না।
catch (e: Exception) { /* কিছুই না */ } — একটি অ্যান্টিপ্যাটার্ন যা অ্যাপ্লিকেশনকে ভুল অবস্থায় কাজ চালিয়ে যেতে বাধ্য করে। যদি আপনি নিশ্চিত হন যে ত্রুটিটি উপেক্ষা করা যেতে পারে — যুক্তি সহ একটি মন্তব্য যোগ করুন। Swift-এ ঐচ্ছিক উপেক্ষার জন্য try? (ত্রুটি -> nil) ব্যবহার করুন। Kotlin-এ — Result<T>.onFailure { /* লগ */ }। লগিং ছাড়া ব্যতিক্রম গ্রাস করবেন না।
যদি একটি ত্রুটি 5+ স্তরের মধ্য দিয়ে হ্যান্ডল না হয়ে যায়, তাহলে আর্কিটেকচার পুনর্বিবেচনা প্রয়োজন। Propagation-এর প্রতিটি স্তর অন্তর্নিহিত ফাংশনের throws সিগনেচারের উপর নির্ভরশীলতা। সমাধান: স্তর সীমানায় Failure-কন্টেইনার (sealed class Result { Success, Error }) ব্যবহার করুন যাতে propagation স্পষ্ট এবং সীমিত হয়। Propagation চেইন যত ছোট হবে, কোড পরীক্ষা এবং ডিবাগ করা তত সহজ।
Kotlin Coroutines-এ, launch-এ ব্যতিক্রম ডিফল্টভাবে প্যারেন্ট করুটিন এবং সমস্ত siblings (একই scope-এর শিশু) বাতিল করে। যদি 10টি সমান্তরাল কাজের মধ্যে একটি ব্যর্থ হয়, বাকি 9টি বাতিল হবে, যা প্রায়শই অনাকাঙ্ক্ষিত। ত্রুটি আলাদা করতে SupervisorJob বা supervisorScope ব্যবহার করুন: একটি শিশুতে ত্রুটি siblings বাতিল করে না। ViewModelScope ডিফল্টভাবে SupervisorJob ব্যবহার করে, যা Android-এ এই সমস্যা থেকে রক্ষা করে।
callback-ভিত্তিক API-তে, ত্রুটি প্রায়শই কলব্যাকের প্যারামিটার হিসাবে পাঠানো হয়। যদি কলব্যাক ত্রুটিটি হ্যান্ডল না করে (বা ভুলভাবে হ্যান্ডল করে), propagation অন্তর্নিহিত হয়ে যায় এবং সহজেই হারিয়ে যায়। সমাধান: async/await (Swift) বা করুটিনে (Kotlin) মাইগ্রেট করুন, যেখানে propagation স্ট্যান্ডার্ড try-catch প্রক্রিয়ার মাধ্যমে কাজ করে। যদি callback অনিবার্য হয় — উভয় ক্ষেত্র হ্যান্ডল করতে Either<Error, T> বা Result<T> ব্যবহার করুন।
প্রায়শই জিজ্ঞাসিত প্রশ্ন
Throw হলো একটি এককালীন ক্রিয়া যা ব্যতিক্রম নিক্ষেপ করে। Error Propagation হলো throw থেকে catch পর্যন্ত একাধিক স্ট্যাক স্তরের মাধ্যমে ত্রুটি পাঠানোর সম্পূর্ণ প্রক্রিয়া। Propagation-এর মধ্যে throw, মধ্যবর্তী ফাংশনের মাধ্যমে স্বয়ংক্রিয় বা ম্যানুয়াল পাঠানো এবং চূড়ান্ত হ্যান্ডলিং অন্তর্ভুক্ত। এটি একটি বিস্তৃত ধারণা যা ত্রুটির জীবনচক্র বর্ণনা করে।
Mock-অবজেক্ট ব্যবহার করুন যা নির্দিষ্ট পরিস্থিতিতে ব্যতিক্রম নিক্ষেপ করে। পরীক্ষা করুন যে ফাংশনটি সঠিকভাবে propagate বা ত্রুটি হ্যান্ডল করে assertThrows (Kotlin/JUnit) বা XCTAssertThrowsError (Swift/XCTest) এর মাধ্যমে। Result-ভিত্তিক propagation-এর জন্য, isSuccess/isError এবং উভয় ক্ষেত্রে মান পরীক্ষা করুন।
Result propagation একটি আর্কিটেকচারাল সীমানার মধ্যে প্রত্যাশিত ত্রুটির (অবৈধ ডেটা, ব্যবসায়িক নিয়ম) জন্য পছন্দনীয়। ব্যতিক্রম অপ্রত্যাশিত ত্রুটির (নেটওয়ার্ক ক্ষতি, I/O ত্রুটি) জন্য ভাল যা উচ্চ স্তরে হ্যান্ডল করা উচিত। ত্রুটিসহ ফলাফল এক্সিকিউশন প্রবাহকে বাধা দেয় না, ব্যতিক্রম বাধা দেয়।
Kotlin Coroutines-এ, launch-এ ব্যতিক্রম স্বয়ংক্রিয়ভাবে siblings-এর বাতিলকরণ সহ CoroutineScope-এর মাধ্যমে propagate হয়। বিচ্ছিন্নতার জন্য supervisorScope বা SupervisorJob ব্যবহার করুন: একটি করুটিনে ত্রুটি অন্যগুলিকে বাতিল করে না। async-এর জন্য, await() কল করার সময় try-catch-এর মাধ্যমে ত্রুটিটি স্পষ্টভাবে হ্যান্ডল করতে হবে, অন্যথায় এটি গ্রাস হবে।
আদর্শ চূড়ান্ত হ্যান্ডলার হলো UI স্তর (ViewController, Fragment/Composable)। শুধুমাত্র এটির ব্যবহারকারী ইন্টারফেসে অ্যাক্সেস রয়েছে এবং এটি বার্তা, Snackbar বা ডায়ালগ দেখাতে পারে। মধ্যবর্তী স্তরগুলি (Repository, UseCase, ViewModel) ত্রুটি propagate করে, প্রয়োজনে আরও বিমূর্ত ডোমেন প্রকারে রূপান্তর করে।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন